audio: Implement a SDL2 backend (#2258)
* audio: Implement a SDL2 backend This adds support to SDL2 as an audio backend. It has the same compatibility level as OpenAL without its issues. I also took the liberty of restructuring the SDL2 code to have one shared project between audio and input. The configuration version was also incremented. * Address gdkchan's comments * Fix update logic * Add an heuristic to pick the correct target sample count wanted by the game * Address gdkchan's comments * Address Ac_k's comments * Fix audren output * Address gdkchan's comments
This commit is contained in:
parent
106512229e
commit
eb056218a1
16 changed files with 511 additions and 14 deletions
|
@ -0,0 +1,13 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
16
Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs
Normal file
16
Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
184
Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
Normal file
184
Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
Normal file
|
@ -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<SDL2HardwareDeviceSession> _sessions;
|
||||||
|
|
||||||
|
public SDL2HardwareDeviceDriver()
|
||||||
|
{
|
||||||
|
_updateRequiredEvent = new ManualResetEvent(false);
|
||||||
|
_sessions = new List<SDL2HardwareDeviceSession>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
223
Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
Normal file
223
Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs
Normal file
|
@ -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<SDL2AudioBuffer> _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<SDL2AudioBuffer>();
|
||||||
|
_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<byte> streamSpan = new Span<byte>((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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@
|
||||||
using Ryujinx.Audio.Common;
|
using Ryujinx.Audio.Common;
|
||||||
using Ryujinx.Audio.Integration;
|
using Ryujinx.Audio.Integration;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ryujinx.Audio.Backends.Common
|
namespace Ryujinx.Audio.Backends.Common
|
||||||
{
|
{
|
||||||
|
@ -52,7 +53,13 @@ namespace Ryujinx.Audio.Backends.Common
|
||||||
|
|
||||||
protected ulong GetSampleCount(AudioBuffer buffer)
|
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();
|
public abstract void Dispose();
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
{
|
{
|
||||||
Dummy,
|
Dummy,
|
||||||
OpenAl,
|
OpenAl,
|
||||||
SoundIo
|
SoundIo,
|
||||||
|
SDL2
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 24;
|
public const int CurrentVersion = 25;
|
||||||
|
|
||||||
public int Version { get; set; }
|
public int Version { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -803,6 +803,13 @@ namespace Ryujinx.Configuration
|
||||||
configurationFileUpdated = true;
|
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;
|
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
|
||||||
Graphics.ResScale.Value = configurationFileFormat.ResScale;
|
Graphics.ResScale.Value = configurationFileFormat.ResScale;
|
||||||
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
|
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
|
||||||
|
|
|
@ -5,12 +5,9 @@
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="ppy.SDL2-CS" Version="1.0.225-alpha" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using Ryujinx.SDL2.Common;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using static SDL2.SDL;
|
using static SDL2.SDL;
|
||||||
|
|
||||||
|
|
15
Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj
Normal file
15
Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ppy.SDL2-CS" Version="1.0.225-alpha" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -4,9 +4,9 @@ using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using static SDL2.SDL;
|
using static SDL2.SDL;
|
||||||
|
|
||||||
namespace Ryujinx.Input.SDL2
|
namespace Ryujinx.SDL2.Common
|
||||||
{
|
{
|
||||||
class SDL2Driver : IDisposable
|
public class SDL2Driver : IDisposable
|
||||||
{
|
{
|
||||||
private static SDL2Driver _instance;
|
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 bool _isRunning;
|
||||||
private uint _refereceCount;
|
private uint _refereceCount;
|
16
Ryujinx.sln
16
Ryujinx.sln
|
@ -59,9 +59,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.Open
|
||||||
EndProject
|
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}"
|
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
|
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
|
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
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
|
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
|
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
|
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
|
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
|
||||||
|
|
|
@ -5,6 +5,7 @@ using LibHac.Common;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
using Ryujinx.Audio.Backends.Dummy;
|
using Ryujinx.Audio.Backends.Dummy;
|
||||||
using Ryujinx.Audio.Backends.OpenAL;
|
using Ryujinx.Audio.Backends.OpenAL;
|
||||||
|
using Ryujinx.Audio.Backends.SDL2;
|
||||||
using Ryujinx.Audio.Backends.SoundIo;
|
using Ryujinx.Audio.Backends.SoundIo;
|
||||||
using Ryujinx.Audio.Integration;
|
using Ryujinx.Audio.Integration;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
@ -327,7 +328,18 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver();
|
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)
|
if (SoundIoHardwareDeviceDriver.IsSupported)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using Gtk;
|
using Gtk;
|
||||||
using Ryujinx.Audio.Backends.OpenAL;
|
using Ryujinx.Audio.Backends.OpenAL;
|
||||||
|
using Ryujinx.Audio.Backends.SDL2;
|
||||||
using Ryujinx.Audio.Backends.SoundIo;
|
using Ryujinx.Audio.Backends.SoundIo;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
@ -302,6 +303,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
|
|
||||||
TreeIter openAlIter = _audioBackendStore.AppendValues("OpenAL", AudioBackend.OpenAl);
|
TreeIter openAlIter = _audioBackendStore.AppendValues("OpenAL", AudioBackend.OpenAl);
|
||||||
TreeIter soundIoIter = _audioBackendStore.AppendValues("SoundIO", AudioBackend.SoundIo);
|
TreeIter soundIoIter = _audioBackendStore.AppendValues("SoundIO", AudioBackend.SoundIo);
|
||||||
|
TreeIter sdl2Iter = _audioBackendStore.AppendValues("SDL2", AudioBackend.SDL2);
|
||||||
TreeIter dummyIter = _audioBackendStore.AppendValues("Dummy", AudioBackend.Dummy);
|
TreeIter dummyIter = _audioBackendStore.AppendValues("Dummy", AudioBackend.Dummy);
|
||||||
|
|
||||||
_audioBackendSelect = ComboBox.NewWithModelAndEntry(_audioBackendStore);
|
_audioBackendSelect = ComboBox.NewWithModelAndEntry(_audioBackendStore);
|
||||||
|
@ -316,6 +318,9 @@ namespace Ryujinx.Ui.Windows
|
||||||
case AudioBackend.SoundIo:
|
case AudioBackend.SoundIo:
|
||||||
_audioBackendSelect.SetActiveIter(soundIoIter);
|
_audioBackendSelect.SetActiveIter(soundIoIter);
|
||||||
break;
|
break;
|
||||||
|
case AudioBackend.SDL2:
|
||||||
|
_audioBackendSelect.SetActiveIter(sdl2Iter);
|
||||||
|
break;
|
||||||
case AudioBackend.Dummy:
|
case AudioBackend.Dummy:
|
||||||
_audioBackendSelect.SetActiveIter(dummyIter);
|
_audioBackendSelect.SetActiveIter(dummyIter);
|
||||||
break;
|
break;
|
||||||
|
@ -328,11 +333,13 @@ namespace Ryujinx.Ui.Windows
|
||||||
|
|
||||||
bool openAlIsSupported = false;
|
bool openAlIsSupported = false;
|
||||||
bool soundIoIsSupported = false;
|
bool soundIoIsSupported = false;
|
||||||
|
bool sdl2IsSupported = false;
|
||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
openAlIsSupported = OpenALHardwareDeviceDriver.IsSupported;
|
openAlIsSupported = OpenALHardwareDeviceDriver.IsSupported;
|
||||||
soundIoIsSupported = SoundIoHardwareDeviceDriver.IsSupported;
|
soundIoIsSupported = SoundIoHardwareDeviceDriver.IsSupported;
|
||||||
|
sdl2IsSupported = SDL2HardwareDeviceDriver.IsSupported;
|
||||||
});
|
});
|
||||||
|
|
||||||
// This function runs whenever the dropdown is opened
|
// This function runs whenever the dropdown is opened
|
||||||
|
@ -342,6 +349,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
{
|
{
|
||||||
AudioBackend.OpenAl => openAlIsSupported,
|
AudioBackend.OpenAl => openAlIsSupported,
|
||||||
AudioBackend.SoundIo => soundIoIsSupported,
|
AudioBackend.SoundIo => soundIoIsSupported,
|
||||||
|
AudioBackend.SDL2 => sdl2IsSupported,
|
||||||
AudioBackend.Dummy => true,
|
AudioBackend.Dummy => true,
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
};
|
};
|
||||||
|
|
Reference in a new issue