diff --git a/src/Ryujinx.Audio.Backends.SDL3/SDL2HardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SDL3/SDL2HardwareDeviceDriver.cs deleted file mode 100644 index d48c65eff..000000000 --- a/src/Ryujinx.Audio.Backends.SDL3/SDL2HardwareDeviceDriver.cs +++ /dev/null @@ -1,208 +0,0 @@ -// using Ryujinx.Audio.Common; -// using Ryujinx.Audio.Integration; -// using Ryujinx.Common.Logging; -// using Ryujinx.Memory; -// using Ryujinx.SDL3.Common; -// using System; -// using System.Collections.Concurrent; -// using System.Runtime.InteropServices; -// using System.Threading; -// using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; -// using static SDL3.SDL; -// -// namespace Ryujinx.Audio.Backends.SDL3 -// { -// public class SDL3HardwareDeviceDriver : IHardwareDeviceDriver -// { -// private readonly ManualResetEvent _updateRequiredEvent; -// private readonly ManualResetEvent _pauseEvent; -// private readonly ConcurrentDictionary _sessions; -// -// private readonly bool _supportSurroundConfiguration; -// -// public float Volume { get; set; } -// -// // TODO: Add this to SDL3-CS -// // NOTE: We use a DllImport here because of marshaling issue for spec. -// #pragma warning disable SYSLIB1054 -// [DllImport("SDL3")] -// private static extern int SDL_GetDefaultAudioInfo(nint name, out SDL_AudioSpec spec, int isCapture); -// #pragma warning restore SYSLIB1054 -// -// public SDL3HardwareDeviceDriver() -// { -// _updateRequiredEvent = new ManualResetEvent(false); -// _pauseEvent = new ManualResetEvent(true); -// _sessions = new ConcurrentDictionary(); -// -// SDL3Driver.Instance.Initialize(); -// -// int res = SDL_GetDefaultAudioInfo(nint.Zero, out var spec, 0); -// -// if (res != 0) -// { -// Logger.Error?.Print(LogClass.Application, -// $"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\""); -// -// _supportSurroundConfiguration = true; -// } -// else -// { -// _supportSurroundConfiguration = spec.channels >= 6; -// } -// -// Volume = 1f; -// } -// -// 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 ManualResetEvent GetPauseEvent() -// { -// return _pauseEvent; -// } -// -// 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 SDL3 backend!"); -// } -// -// SDL3HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount); -// -// _sessions.TryAdd(session, 0); -// -// return session; -// } -// -// internal bool Unregister(SDL3HardwareDeviceSession session) -// { -// return _sessions.TryRemove(session, out _); -// } -// -// private static SDL_AudioSpec GetSDL3Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount) -// { -// return new SDL_AudioSpec -// { -// channels = (byte)requestedChannelCount, -// format = GetSDL3Format(requestedSampleFormat), -// freq = (int)requestedSampleRate, -// samples = (ushort)sampleCount, -// }; -// } -// -// internal static ushort GetSDL3Format(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}"), -// }; -// } -// -// internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL_AudioCallback callback) -// { -// SDL_AudioSpec desired = GetSDL3Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, sampleCount); -// -// desired.callback = callback; -// -// uint device = SDL_OpenAudioDevice(nint.Zero, 0, ref desired, out SDL_AudioSpec got, 0); -// -// if (device == 0) -// { -// Logger.Error?.Print(LogClass.Application, $"SDL3 open audio device initialization failed with error \"{SDL_GetError()}\""); -// -// return 0; -// } -// -// bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels; -// -// if (!isValid) -// { -// Logger.Error?.Print(LogClass.Application, "SDL3 open audio device is not valid"); -// SDL_CloseAudioDevice(device); -// -// return 0; -// } -// -// return device; -// } -// -// public void Dispose() -// { -// GC.SuppressFinalize(this); -// Dispose(true); -// } -// -// protected virtual void Dispose(bool disposing) -// { -// if (disposing) -// { -// foreach (SDL3HardwareDeviceSession session in _sessions.Keys) -// { -// session.Dispose(); -// } -// -// SDL3Driver.Instance.Dispose(); -// -// _pauseEvent.Dispose(); -// } -// } -// -// public bool SupportsSampleRate(uint sampleRate) -// { -// return true; -// } -// -// public bool SupportsSampleFormat(SampleFormat sampleFormat) -// { -// return sampleFormat != SampleFormat.PcmInt24; -// } -// -// public bool SupportsChannelCount(uint channelCount) -// { -// if (channelCount == 6) -// { -// return _supportSurroundConfiguration; -// } -// -// return true; -// } -// -// public bool SupportsDirection(Direction direction) -// { -// // TODO: add direction input when supported. -// return direction == Direction.Output; -// } -// } -// } diff --git a/src/Ryujinx.Audio.Backends.SDL3/SDL2HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL3/SDL2HardwareDeviceSession.cs deleted file mode 100644 index 7e8af4af1..000000000 --- a/src/Ryujinx.Audio.Backends.SDL3/SDL2HardwareDeviceSession.cs +++ /dev/null @@ -1,234 +0,0 @@ -// using Ryujinx.Audio.Backends.Common; -// using Ryujinx.Audio.Common; -// using Ryujinx.Common.Logging; -// using Ryujinx.Common.Memory; -// using Ryujinx.Memory; -// using System; -// using System.Buffers; -// using System.Collections.Concurrent; -// using System.Threading; -// -// using static SDL3.SDL; -// -// namespace Ryujinx.Audio.Backends.SDL3 -// { -// class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase -// { -// private readonly SDL3HardwareDeviceDriver _driver; -// private readonly ConcurrentQueue _queuedBuffers; -// private readonly DynamicRingBuffer _ringBuffer; -// private ulong _playedSampleCount; -// private readonly ManualResetEvent _updateRequiredEvent; -// private uint _outputStream; -// private bool _hasSetupError; -// private readonly SDL_AudioCallback _callbackDelegate; -// private readonly int _bytesPerFrame; -// private uint _sampleCount; -// private bool _started; -// private float _volume; -// private readonly ushort _nativeSampleFormat; -// -// public SDL3HardwareDeviceSession(SDL3HardwareDeviceDriver 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 = SDL3HardwareDeviceDriver.GetSDL3Format(RequestedSampleFormat); -// _sampleCount = uint.MaxValue; -// _started = false; -// _volume = 1f; -// } -// -// private void EnsureAudioStreamSetup(AudioBuffer buffer) -// { -// uint bufferSampleCount = (uint)GetSampleCount(buffer); -// bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) || -// (bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount); -// -// if (needAudioSetup) -// { -// _sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount); -// -// uint newOutputStream = SDL3HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate); -// -// _hasSetupError = newOutputStream == 0; -// -// if (!_hasSetupError) -// { -// 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}"); -// } -// } -// } -// -// private unsafe void Update(nint userdata, nint stream, int streamLength) -// { -// Span streamSpan = new((void*)stream, streamLength); -// -// int maxFrameCount = (int)GetSampleCount(streamLength); -// int bufferedFrames = _ringBuffer.Length / _bytesPerFrame; -// -// int frameCount = Math.Min(bufferedFrames, maxFrameCount); -// -// if (frameCount == 0) -// { -// // SDL3 left the responsibility to the user to clear the buffer. -// streamSpan.Clear(); -// -// return; -// } -// -// using SpanOwner samplesOwner = SpanOwner.Rent(frameCount * _bytesPerFrame); -// -// Span samples = samplesOwner.Span; -// -// _ringBuffer.Read(samples, 0, samples.Length); -// -// fixed (byte* p = samples) -// { -// nint pStreamSrc = (nint)p; -// -// // Zero the dest buffer -// streamSpan.Clear(); -// -// // Apply volume to written data -// SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME)); -// } -// -// ulong sampleCount = GetSampleCount(samples.Length); -// -// ulong availaibleSampleCount = sampleCount; -// -// bool needUpdate = false; -// -// while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL3AudioBuffer 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); -// -// if (_outputStream != 0) -// { -// SDL3AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer)); -// -// _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); -// -// _queuedBuffers.Enqueue(driverBuffer); -// } -// else -// { -// Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer)); -// -// _updateRequiredEvent.Set(); -// } -// } -// -// 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 SDL3AudioBuffer driverBuffer)) -// { -// return true; -// } -// -// return driverBuffer.DriverIdentifier != buffer.DataPointer; -// } -// -// protected virtual void Dispose(bool disposing) -// { -// if (disposing && _driver.Unregister(this)) -// { -// PrepareToClose(); -// Stop(); -// -// if (_outputStream != 0) -// { -// SDL_CloseAudioDevice(_outputStream); -// } -// } -// } -// -// public override void Dispose() -// { -// Dispose(true); -// } -// } -// } diff --git a/src/Ryujinx.Audio.Backends.SDL3/SDL2AudioBuffer.cs b/src/Ryujinx.Audio.Backends.SDL3/SDL3AudioBuffer.cs similarity index 100% rename from src/Ryujinx.Audio.Backends.SDL3/SDL2AudioBuffer.cs rename to src/Ryujinx.Audio.Backends.SDL3/SDL3AudioBuffer.cs diff --git a/src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceDriver.cs new file mode 100644 index 000000000..04ff7b8c5 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceDriver.cs @@ -0,0 +1,217 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using Ryujinx.SDL3.Common; +using System; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using System.Threading; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; +using static SDL3.SDL; + +namespace Ryujinx.Audio.Backends.SDL3 +{ + public class SDL3HardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + private readonly ConcurrentDictionary _sessions; + + private readonly bool _supportSurroundConfiguration; + + public float Volume { get; set; } + + public SDL3HardwareDeviceDriver() + { + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + + SDL3Driver.Instance.Initialize(); + + if (!SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, out var spec, out int sample_frames)) + { + Logger.Error?.Print(LogClass.Application, + $"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\""); + + _supportSurroundConfiguration = true; + } + else + { + _supportSurroundConfiguration = spec.channels >= 6; + } + + Volume = 1f; + } + + public static bool IsSupported => IsSupportedInternal(); + + private static bool IsSupportedInternal() + { + var devices = SDL_GetAudioPlaybackDevices(out int count); + var joystickIDs = new int[count]; + Marshal.Copy(devices, joystickIDs, 0, count); + for (int i = 0; i < count; ++i) + { + Console.WriteLine(joystickIDs[i]); + } + + var 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 ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + 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 SDL3 backend!"); + } + + SDL3HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount); + + _sessions.TryAdd(session, 0); + + return session; + } + + internal bool Unregister(SDL3HardwareDeviceSession session) + { + return _sessions.TryRemove(session, out _); + } + + private static SDL_AudioSpec GetSDL3Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, + uint requestedChannelCount, uint sampleCount) + { + return new SDL_AudioSpec + { + channels = (byte)requestedChannelCount, + format = GetSDL3Format(requestedSampleFormat), + freq = (int)requestedSampleRate, + }; + } + + internal static SDL_AudioFormat GetSDL3Format(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt8 => SDL_AudioFormat.SDL_AUDIO_S8, + SampleFormat.PcmInt16 => SDL_AudioFormat.SDL_AUDIO_S16, + SampleFormat.PcmInt32 => SDL_AudioFormat.SDL_AUDIO_S32, + SampleFormat.PcmFloat => SDL_AudioFormat.SDL_AUDIO_F32, + _ => throw new ArgumentException($"Unsupported sample format {format}"), + }; + } + + internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, + uint requestedChannelCount, uint sampleCount, SDL_AudioStreamCallback callback) + { + SDL_AudioSpec desired = GetSDL3Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, + sampleCount); + SDL_AudioSpec desired2 = default; + var device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, ref desired); + + if (device == 0) + { + Logger.Error?.Print(LogClass.Application, + $"SDL3 open audio device initialization failed with error \"{SDL_GetError()}\""); + + return 0; + } + + Console.WriteLine(SDL_GetAudioDeviceName(device)); + bool isValid = false; + if (SDL_GetAudioDeviceFormat(device, out SDL_AudioSpec got, out int i)) + { + Console.WriteLine(got.freq); + isValid = got.format == desired.format && got.freq == desired.freq && + got.channels == desired.channels; + } + + // if (!isValid) + // { + // Logger.Error?.Print(LogClass.Application, "SDL3 open audio device is not valid"); + // SDL_CloseAudioDevice(device); + // + // return 0; + // } + + return device; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (SDL3HardwareDeviceSession session in _sessions.Keys) + { + session.Dispose(); + } + + SDL3Driver.Instance.Dispose(); + + _pauseEvent.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return sampleFormat != SampleFormat.PcmInt24; + } + + public bool SupportsChannelCount(uint channelCount) + { + if (channelCount == 6) + { + return _supportSurroundConfiguration; + } + + return true; + } + + public bool SupportsDirection(Direction direction) + { + // TODO: add direction input when supported. + return direction == Direction.Output; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceSession.cs new file mode 100644 index 000000000..8eaf000e0 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceSession.cs @@ -0,0 +1,235 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Collections.Concurrent; +using System.Threading; + +using static SDL3.SDL; + +namespace Ryujinx.Audio.Backends.SDL3 +{ + class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private readonly SDL3HardwareDeviceDriver _driver; + private readonly ConcurrentQueue _queuedBuffers; + private readonly DynamicRingBuffer _ringBuffer; + private ulong _playedSampleCount; + private readonly ManualResetEvent _updateRequiredEvent; + private uint _outputStream; + private bool _hasSetupError; + private readonly SDL_AudioStreamCallback _callbackDelegate; + private readonly int _bytesPerFrame; + private uint _sampleCount; + private bool _started; + private float _volume; + private readonly SDL_AudioFormat _nativeSampleFormat; + + public SDL3HardwareDeviceSession(SDL3HardwareDeviceDriver 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 = SDL3HardwareDeviceDriver.GetSDL3Format(RequestedSampleFormat); + _sampleCount = uint.MaxValue; + _started = false; + _volume = 1f; + } + + private void EnsureAudioStreamSetup(AudioBuffer buffer) + { + uint bufferSampleCount = (uint)GetSampleCount(buffer); + bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) || + (bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount); + + if (needAudioSetup) + { + _sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount); + + var newOutputStream = SDL3HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate); + + _hasSetupError = newOutputStream == 0; + + if (!_hasSetupError) + { + if (_outputStream != 0) + { + SDL_CloseAudioDevice(_outputStream); + } + + _outputStream = newOutputStream; + SDL_PauseAudioDevice(_outputStream); + + Logger.Info?.Print(LogClass.Audio, $"New audio stream setup with a target sample count of {_sampleCount}"); + } + } + } + private unsafe void Update(IntPtr userdata, IntPtr stream, int streamLength, int total_amount) + { + Console.WriteLine("call"); + Console.WriteLine(SDL_GetAudioDeviceName(SDL_GetAudioStreamDevice(stream))); + Span streamSpan = new((void*)stream, streamLength); + + int maxFrameCount = (int)GetSampleCount(streamLength); + int bufferedFrames = _ringBuffer.Length / _bytesPerFrame; + + int frameCount = Math.Min(bufferedFrames, maxFrameCount); + + if (frameCount == 0) + { + // SDL3 left the responsibility to the user to clear the buffer. + streamSpan.Clear(); + + return; + } + + using SpanOwner samplesOwner = SpanOwner.Rent(frameCount * _bytesPerFrame); + + Span samples = samplesOwner.Span; + + _ringBuffer.Read(samples, 0, samples.Length); + + fixed (byte* p = samples) + { + nint pStreamSrc = (nint)p; + + // Zero the dest buffer + streamSpan.Clear(); + + // Apply volume to written data + // SDL_MixAudio(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, _driver.Volume * _volume); + } + + ulong sampleCount = GetSampleCount(samples.Length); + + ulong availaibleSampleCount = sampleCount; + + bool needUpdate = false; + + while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL3AudioBuffer 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) + { + Console.WriteLine(buffer); + EnsureAudioStreamSetup(buffer); + + if (_outputStream != 0) + { + SDL3AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer)); + + _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); + + _queuedBuffers.Enqueue(driverBuffer); + } + else + { + Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer)); + + _updateRequiredEvent.Set(); + } + } + + public override void SetVolume(float volume) + { + _volume = volume; + } + + public override void Start() + { + if (!_started) + { + if (_outputStream != 0) + { + SDL_ResumeAudioDevice(_outputStream); + } + + _started = true; + } + } + + public override void Stop() + { + if (_started) + { + if (_outputStream != 0) + { + SDL_PauseAudioDevice(_outputStream); + } + + _started = false; + } + } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + if (!_queuedBuffers.TryPeek(out SDL3AudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _driver.Unregister(this)) + { + PrepareToClose(); + Stop(); + + if (_outputStream != 0) + { + SDL_CloseAudioDevice(_outputStream); + } + } + } + + public override void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs index 67b6b5530..eebbbda31 100644 --- a/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Keyboard.cs @@ -193,7 +193,7 @@ namespace Ryujinx.Input.SDL3 } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int ToSDL2Scancode(Key key) + private static int ToSDL3Scancode(Key key) { if (key >= Key.Unknown && key <= Key.Menu) { @@ -235,7 +235,7 @@ namespace Ryujinx.Input.SDL3 for (Key key = 0; key < Key.Count; key++) { - int index = ToSDL2Scancode(key); + int index = ToSDL3Scancode(key); if (index == -1) { SDL_Keymod modifierMask = GetKeyboardModifierMask(key); diff --git a/src/Ryujinx.SDL3-CS/SDL3.cs b/src/Ryujinx.SDL3-CS/SDL3.cs index 6f636dd69..d1f50c76f 100644 --- a/src/Ryujinx.SDL3-CS/SDL3.cs +++ b/src/Ryujinx.SDL3-CS/SDL3.cs @@ -8048,5 +8048,6 @@ public static unsafe partial class SDL [UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])] public static partial int SDL_EnterAppMainCallbacks(int argc, IntPtr argv, SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit); + public const uint SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK = 0xFFFFFFFFu; } diff --git a/src/Ryujinx.SDL3.Common/SDL3Driver.cs b/src/Ryujinx.SDL3.Common/SDL3Driver.cs index 4a2254bdf..bcdd89e3f 100644 --- a/src/Ryujinx.SDL3.Common/SDL3Driver.cs +++ b/src/Ryujinx.SDL3.Common/SDL3Driver.cs @@ -25,7 +25,7 @@ namespace Ryujinx.SDL3.Common public static Action MainThreadDispatcher { get; set; } - private const SDL_InitFlags SdlInitFlags = SDL_InitFlags.SDL_INIT_GAMEPAD; + private const SDL_InitFlags SdlInitFlags = SDL_InitFlags.SDL_INIT_GAMEPAD | SDL_InitFlags.SDL_INIT_AUDIO; private bool _isRunning; private uint _refereceCount; @@ -52,13 +52,13 @@ namespace Ryujinx.SDL3.Common return; } - // SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx"); - // SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); - // SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); - // SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); - // SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0"); - // SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); - // SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); + SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); + SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); // // // // NOTE: As of SDL2 2.24.0, joycons are combined by default but the motion source only come from one of them. @@ -67,7 +67,7 @@ namespace Ryujinx.SDL3.Common if (!SDL_Init(SdlInitFlags)) { - string errorMessage = $"SDL2 initialization failed with error \"{SDL_GetError()}\""; + string errorMessage = $"SDL3 initialization failed with error \"{SDL_GetError()}\""; Logger.Error?.Print(LogClass.Application, errorMessage); diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs index a35a79e86..baf805d6a 100644 --- a/src/Ryujinx/AppHost.cs +++ b/src/Ryujinx/AppHost.cs @@ -9,6 +9,7 @@ using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.Dummy; using Ryujinx.Audio.Backends.OpenAL; using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SDL3; using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Audio.Integration; using Ryujinx.Ava.Common; @@ -957,6 +958,7 @@ namespace Ryujinx.Ava { var availableBackends = new List { + AudioBackend.SDL3, AudioBackend.SDL2, AudioBackend.SoundIo, AudioBackend.OpenAl, @@ -996,6 +998,7 @@ namespace Ryujinx.Ava deviceDriver = currentBackend switch { + AudioBackend.SDL3 => InitializeAudioBackend(AudioBackend.SDL3, nextBackend), AudioBackend.SDL2 => InitializeAudioBackend(AudioBackend.SDL2, nextBackend), AudioBackend.SoundIo => InitializeAudioBackend(AudioBackend.SoundIo, nextBackend), AudioBackend.OpenAl => InitializeAudioBackend(AudioBackend.OpenAl, nextBackend), diff --git a/src/Ryujinx/Headless/WindowBase.cs b/src/Ryujinx/Headless/WindowBase.cs index d89638cc1..e6436b0a2 100644 --- a/src/Ryujinx/Headless/WindowBase.cs +++ b/src/Ryujinx/Headless/WindowBase.cs @@ -15,6 +15,7 @@ using Ryujinx.Input; using Ryujinx.Input.HLE; using Ryujinx.Input.SDL2; using Ryujinx.SDL2.Common; +using Ryujinx.SDL3.Common; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -116,6 +117,7 @@ namespace Ryujinx.Headless HostUITheme = new HeadlessHostUiTheme(); SDL2Driver.Instance.Initialize(); + SDL3Driver.Instance.Initialize(); } public void Initialize(Switch device, List inputConfigs, bool enableKeyboard, bool enableMouse) diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index e7605d5a5..106124cb7 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -71,6 +71,7 @@ + diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 2678bbf98..612c014d1 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -6,6 +6,7 @@ using Gommon; using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.OpenAL; using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SDL3; using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Helpers; @@ -212,6 +213,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool IsOpenAlEnabled { get; set; } public bool IsSoundIoEnabled { get; set; } public bool IsSDL2Enabled { get; set; } + public bool IsSDL3Enabled { get; set; } public bool IsCustomResolutionScaleActive => _resolutionScale == 4; public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr; @@ -373,6 +375,7 @@ namespace Ryujinx.Ava.UI.ViewModels IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported; IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported; IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported; + IsSDL3Enabled = SDL3HardwareDeviceDriver.IsSupported; await Dispatcher.UIThread.InvokeAsync(() => { diff --git a/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml index 2f9ae65a0..bdaf0eba8 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsAudioView.axaml @@ -46,6 +46,12 @@ + + + + + + diff --git a/src/Ryujinx/Utilities/Configuration/AudioBackend.cs b/src/Ryujinx/Utilities/Configuration/AudioBackend.cs index 8394bb282..836725d3d 100644 --- a/src/Ryujinx/Utilities/Configuration/AudioBackend.cs +++ b/src/Ryujinx/Utilities/Configuration/AudioBackend.cs @@ -10,5 +10,6 @@ namespace Ryujinx.Ava.Utilities.Configuration OpenAl, SoundIo, SDL2, + SDL3, } }