ryujinx/Ryujinx.SDL2.Common/SDL2Driver.cs
Mary eb056218a1
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
2021-05-05 23:37:09 +02:00

171 lines
5.3 KiB
C#

using Ryujinx.Common.Logging;
using System;
using System.IO;
using System.Threading;
using static SDL2.SDL;
namespace Ryujinx.SDL2.Common
{
public class SDL2Driver : IDisposable
{
private static SDL2Driver _instance;
public static bool IsInitialized => _instance != null;
public static SDL2Driver Instance
{
get
{
if (_instance == null)
{
_instance = new SDL2Driver();
}
return _instance;
}
}
private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO;
private bool _isRunning;
private uint _refereceCount;
private Thread _worker;
public event Action<int, int> OnJoyStickConnected;
public event Action<int> OnJoystickDisconnected;
private object _lock = new object();
private SDL2Driver() {}
public void Initialize()
{
lock (_lock)
{
_refereceCount++;
if (_isRunning)
{
return;
}
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");
// TODO: Add this in nuget package once SDL2 2.0.15 hit stable release.
SDL_SetHint("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0");
SDL_SetHint("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1");
if (SDL_Init(SdlInitFlags) != 0)
{
string errorMessage = $"SDL2 initlaization failed with error \"{SDL_GetError()}\"";
Logger.Error?.Print(LogClass.Application, errorMessage);
throw new Exception(errorMessage);
}
// First ensure that we only enable joystick events (for connected/disconnected).
SDL_GameControllerEventState(SDL_DISABLE);
SDL_JoystickEventState(SDL_ENABLE);
// Disable all joysticks information, we don't need them no need to flood the event queue for that.
SDL_EventState(SDL_EventType.SDL_JOYAXISMOTION, SDL_DISABLE);
SDL_EventState(SDL_EventType.SDL_JOYBALLMOTION, SDL_DISABLE);
SDL_EventState(SDL_EventType.SDL_JOYHATMOTION, SDL_DISABLE);
SDL_EventState(SDL_EventType.SDL_JOYBUTTONDOWN, SDL_DISABLE);
SDL_EventState(SDL_EventType.SDL_JOYBUTTONUP, SDL_DISABLE);
SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE);
string gamepadDbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SDL_GameControllerDB.txt");
if (File.Exists(gamepadDbPath))
{
SDL_GameControllerAddMappingsFromFile(gamepadDbPath);
}
_worker = new Thread(EventWorker);
_isRunning = true;
_worker.Start();
}
}
private void HandleSDLEvent(ref SDL_Event evnt)
{
if (evnt.type == SDL_EventType.SDL_JOYDEVICEADDED)
{
int deviceId = evnt.cbutton.which;
// SDL2 loves to be inconsistent here by providing the device id instead of the instance id (like on removed event), as such we just grab it and send it inside our system.
int instanceId = SDL_JoystickGetDeviceInstanceID(deviceId);
if (instanceId == -1)
{
return;
}
Logger.Debug?.Print(LogClass.Application, $"Added joystick instance id {instanceId}");
OnJoyStickConnected?.Invoke(deviceId, instanceId);
}
else if (evnt.type == SDL_EventType.SDL_JOYDEVICEREMOVED)
{
Logger.Debug?.Print(LogClass.Application, $"Removed joystick instance id {evnt.cbutton.which}");
OnJoystickDisconnected?.Invoke(evnt.cbutton.which);
}
}
private void EventWorker()
{
const int WaitTimeMs = 10;
using ManualResetEventSlim waitHandle = new ManualResetEventSlim(false);
while (_isRunning)
{
while (SDL_PollEvent(out SDL_Event evnt) != 0)
{
HandleSDLEvent(ref evnt);
}
waitHandle.Wait(WaitTimeMs);
}
}
protected virtual void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
lock (_lock)
{
if (_isRunning)
{
_refereceCount--;
if (_refereceCount == 0)
{
_isRunning = false;
_worker?.Join();
SDL_Quit();
OnJoyStickConnected = null;
OnJoystickDisconnected = null;
}
}
}
}
public void Dispose()
{
Dispose(true);
}
}
}