Update SDL2 to SDL3

- Fix: Crash when connecting a JoyCon

- Fix: Make controller GUIDs match old SDL2 GUIDs

- Fix: Detect face button layout for gamepads

Co-authored-by: Maki <77-maki@users.noreply.git.ryujinx.app>
This commit is contained in:
KeatonTheBot
2026-05-16 18:25:16 -05:00
co-authored by Maki
parent f3a87af51d
commit e7c720fd2c
58 changed files with 940 additions and 877 deletions
+1 -1
View File
@@ -38,7 +38,7 @@
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Windows" Version="6.1.3-build5" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.Windows" Version="6.1.3-build5" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.128" /> <PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.128" />
<PackageVersion Include="Ryujinx.SDL2-CS-Redux" Version="2.32.10" /> <PackageVersion Include="ppy.SDL3-CS" Version="2026.320.0" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" /> <PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Shaderc" Version="2.23.0" /> <PackageVersion Include="Silk.NET.Shaderc" Version="2.23.0" />
+47
View File
@@ -0,0 +1,47 @@
<Solution>
<Folder Name="/Solution Items/">
<File Path=".editorconfig" />
<File Path="Directory.Build.props" />
<File Path="Directory.Packages.props" />
<File Path="nuget.config" />
</Folder>
<Project Path="src/ARMeilleure/ARMeilleure.csproj" />
<Project Path="src/LibKenjinx/LibKenjinx.csproj" />
<Project Path="src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj" />
<Project Path="src/Ryujinx.Audio.Backends.SDL3/Ryujinx.Audio.Backends.SDL3.csproj" />
<Project Path="src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj" />
<Project Path="src/Ryujinx.Audio/Ryujinx.Audio.csproj" />
<Project Path="src/Ryujinx.Common/Ryujinx.Common.csproj" />
<Project Path="src/Ryujinx.Cpu/Ryujinx.Cpu.csproj" />
<Project Path="src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj" />
<Project Path="src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj" />
<Project Path="src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj" />
<Project Path="src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj" />
<Project Path="src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj" />
<Project Path="src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj" />
<Project Path="src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj" />
<Project Path="src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj" />
<Project Path="src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj" />
<Project Path="src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj" />
<Project Path="src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj" />
<Project Path="src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj" />
<Project Path="src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj" />
<Project Path="src/Ryujinx.HLE.Generators/Ryujinx.HLE.Generators.csproj" />
<Project Path="src/Ryujinx.HLE/Ryujinx.HLE.csproj" />
<Project Path="src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj" />
<Project Path="src/Ryujinx.Horizon.Generators/Ryujinx.Horizon.Generators.csproj" />
<Project Path="src/Ryujinx.Horizon.Kernel.Generators/Ryujinx.Horizon.Kernel.Generators.csproj" />
<Project Path="src/Ryujinx.Horizon/Ryujinx.Horizon.csproj" />
<Project Path="src/Ryujinx.Input.SDL3/Ryujinx.Input.SDL3.csproj" />
<Project Path="src/Ryujinx.Input/Ryujinx.Input.csproj" />
<Project Path="src/Ryujinx.Memory/Ryujinx.Memory.csproj" />
<Project Path="src/Ryujinx.SDL3.Common/Ryujinx.SDL3.Common.csproj" />
<Project Path="src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj" />
<Project Path="src/Ryujinx.Tests.Memory/Ryujinx.Tests.Memory.csproj" />
<Project Path="src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj" />
<Project Path="src/Ryujinx.Tests/Ryujinx.Tests.csproj" />
<Project Path="src/Ryujinx.UI.Common/Ryujinx.UI.Common.csproj" />
<Project Path="src/Ryujinx.UI.LocaleGenerator/Ryujinx.UI.LocaleGenerator.csproj" />
<Project Path="src/Ryujinx/Ryujinx.csproj" />
<Project Path="src/Spv.Generator/Spv.Generator.csproj" />
</Solution>
+1 -1
View File
@@ -8,7 +8,7 @@ Intro to Kenji-NX
Kenji-NX is an open-source Nintendo Switch emulator written in C#. It is based on Ryujinx, which was originally created by gdkchan. Kenji-NX is an open-source Nintendo Switch emulator written in C#. It is based on Ryujinx, which was originally created by gdkchan.
* The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions. * The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions.
* The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. * The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively.
* Audio output is entirely supported via C# wrappers for SDL2, with OpenAL & libsoundio as fallbacks. * Audio output is entirely supported via C# wrappers for SDL3, with OpenAL & libsoundio as fallbacks.
Getting Started Getting Started
=============== ===============
+1 -1
View File
@@ -113,7 +113,7 @@ namespace LibKenjinx
return new StandardControllerInputConfig return new StandardControllerInputConfig
{ {
Version = InputConfig.CurrentVersion, Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2, Backend = InputBackendType.GamepadSDL3,
Id = null, Id = null,
ControllerType = ControllerType.ProController, ControllerType = ControllerType.ProController,
DeadzoneLeft = 0.1f, DeadzoneLeft = 0.1f,
+2 -2
View File
@@ -12,7 +12,7 @@ using LibHac.Tools.FsSystem.NcaUtils;
using LibKenjinx.Android; using LibKenjinx.Android;
using OpenTK.Audio.OpenAL; using OpenTK.Audio.OpenAL;
using Ryujinx.Audio.Backends.Dummy; using Ryujinx.Audio.Backends.Dummy;
using Ryujinx.Audio.Backends.SDL2; using Ryujinx.Audio.Backends.SDL3;
using Ryujinx.Audio.Integration; using Ryujinx.Audio.Integration;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
@@ -96,7 +96,7 @@ namespace LibKenjinx
public static void InitializeAudio() public static void InitializeAudio()
{ {
AudioDriver = new SDL2HardwareDeviceDriver(); AudioDriver = new SDL3HardwareDeviceDriver();
} }
public static GameStats GetGameStats() public static GameStats GetGameStats()
+1 -1
View File
@@ -27,7 +27,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<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.SDL3\Ryujinx.Audio.Backends.SDL3.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" /> <ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.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" />
@@ -7,7 +7,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" /> <ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
<ProjectReference Include="..\Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj" /> <ProjectReference Include="..\Ryujinx.SDL3.Common\Ryujinx.SDL3.Common.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,12 +1,12 @@
namespace Ryujinx.Audio.Backends.SDL2 namespace Ryujinx.Audio.Backends.SDL3
{ {
class SDL2AudioBuffer class SDL3AudioBuffer
{ {
public readonly ulong DriverIdentifier; public readonly ulong DriverIdentifier;
public readonly ulong SampleCount; public readonly ulong SampleCount;
public ulong SamplePlayed; public ulong SamplePlayed;
public SDL2AudioBuffer(ulong driverIdentifier, ulong sampleCount) public SDL3AudioBuffer(ulong driverIdentifier, ulong sampleCount)
{ {
DriverIdentifier = driverIdentifier; DriverIdentifier = driverIdentifier;
SampleCount = sampleCount; SampleCount = sampleCount;
@@ -2,44 +2,41 @@ using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration; using Ryujinx.Audio.Integration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.SDL2.Common; using Ryujinx.SDL3.Common;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
using static SDL2.SDL; using SDL;
using static SDL.SDL3;
using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Backends.SDL2
namespace Ryujinx.Audio.Backends.SDL3
{ {
public sealed class SDL2HardwareDeviceDriver : IHardwareDeviceDriver
using unsafe SDL_AudioStreamCallbackPointer = delegate* unmanaged[Cdecl]<nint, SDL_AudioStream*, int, int, void>;
public sealed class SDL3HardwareDeviceDriver : IHardwareDeviceDriver
{ {
private readonly ManualResetEvent _updateRequiredEvent; private readonly ManualResetEvent _updateRequiredEvent;
private readonly ManualResetEvent _pauseEvent; private readonly ManualResetEvent _pauseEvent;
private readonly ConcurrentDictionary<SDL2HardwareDeviceSession, byte> _sessions; private readonly ConcurrentDictionary<SDL3HardwareDeviceSession, byte> _sessions;
private readonly bool _supportSurroundConfiguration; private readonly bool _supportSurroundConfiguration;
public float Volume { get; set; } public float Volume { get; set; }
// TODO: Add this to SDL2-CS public unsafe SDL3HardwareDeviceDriver()
// NOTE: We use a DllImport here because of marshaling issue for spec.
#pragma warning disable SYSLIB1054
[DllImport("SDL2")]
private static extern int SDL_GetDefaultAudioInfo(nint name, out SDL_AudioSpec spec, int isCapture);
#pragma warning restore SYSLIB1054
public SDL2HardwareDeviceDriver()
{ {
_updateRequiredEvent = new ManualResetEvent(false); _updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true); _pauseEvent = new ManualResetEvent(true);
_sessions = new ConcurrentDictionary<SDL2HardwareDeviceSession, byte>(); _sessions = new ConcurrentDictionary<SDL3HardwareDeviceSession, byte>();
SDL2Driver.Instance.Initialize(); SDL3Driver.Instance.Initialize();
int res = SDL_GetDefaultAudioInfo(nint.Zero, out var spec, 0); SDL_AudioSpec spec;
if (!SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, null))
if (res != 0)
{ {
Logger.Error?.Print(LogClass.Application, Logger.Error?.Print(LogClass.Application,
$"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\""); $"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\"");
@@ -56,16 +53,16 @@ namespace Ryujinx.Audio.Backends.SDL2
public static bool IsSupported => IsSupportedInternal(); public static bool IsSupported => IsSupportedInternal();
private static bool IsSupportedInternal() private unsafe static bool IsSupportedInternal()
{ {
uint device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, Constants.TargetSampleCount, null); SDL_AudioStream* device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, Constants.TargetSampleCount, null);
if (device != 0) if (device != null)
{ {
SDL_CloseAudioDevice(device); SDL_DestroyAudioStream(device);
} }
return device != 0; return device != null;
} }
public ManualResetEvent GetUpdateRequiredEvent() public ManualResetEvent GetUpdateRequiredEvent()
@@ -92,67 +89,68 @@ namespace Ryujinx.Audio.Backends.SDL2
if (direction != Direction.Output) if (direction != Direction.Output)
{ {
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!"); throw new NotImplementedException("Input direction is currently not implemented on SDL3 backend!");
} }
SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount); SDL3HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0); _sessions.TryAdd(session, 0);
return session; return session;
} }
internal bool Unregister(SDL2HardwareDeviceSession session) internal bool Unregister(SDL3HardwareDeviceSession session)
{ {
return _sessions.TryRemove(session, out _); return _sessions.TryRemove(session, out _);
} }
private static SDL_AudioSpec GetSDL2Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount) private static SDL_AudioSpec GetSDL3Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount)
{ {
return new SDL_AudioSpec return new SDL_AudioSpec
{ {
channels = (byte)requestedChannelCount, channels = (byte)requestedChannelCount,
format = GetSDL2Format(requestedSampleFormat), format = GetSDL3Format(requestedSampleFormat),
freq = (int)requestedSampleRate, freq = (int)requestedSampleRate,
samples = (ushort)sampleCount,
}; };
} }
internal static ushort GetSDL2Format(SampleFormat format) internal static SDL_AudioFormat GetSDL3Format(SampleFormat format)
{ {
return format switch return format switch
{ {
SampleFormat.PcmInt8 => AUDIO_S8, SampleFormat.PcmInt8 => SDL_AudioFormat.SDL_AUDIO_S8,
SampleFormat.PcmInt16 => AUDIO_S16, SampleFormat.PcmInt16 => SDL_AudioFormat.SDL_AUDIO_S16LE,
SampleFormat.PcmInt32 => AUDIO_S32, SampleFormat.PcmInt32 => SDL_AudioFormat.SDL_AUDIO_S32LE,
SampleFormat.PcmFloat => AUDIO_F32, SampleFormat.PcmFloat => SDL_AudioFormat.SDL_AUDIO_F32LE,
_ => throw new ArgumentException($"Unsupported sample format {format}"), _ => throw new ArgumentException($"Unsupported sample format {format}"),
}; };
} }
internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL_AudioCallback callback) internal unsafe static SDL_AudioStream* OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL3HardwareDeviceSession.SDL_AudioStreamCallback callback)
{ {
SDL_AudioSpec desired = GetSDL2Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, sampleCount); SDL_AudioSpec desired = GetSDL3Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount);
SDL_AudioSpec got = desired;
var pCallback = callback != null ? (SDL_AudioStreamCallbackPointer)Marshal.GetFunctionPointerForDelegate(callback) : null;
desired.callback = callback; // From SDL 3 and on, SDL requires us to set this as a hint
SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, $"{sampleCount}");
SDL_AudioStream* device = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &got, pCallback, 0);
uint device = SDL_OpenAudioDevice(nint.Zero, 0, ref desired, out SDL_AudioSpec got, 0); if (device == null)
if (device == 0)
{ {
Logger.Error?.Print(LogClass.Application, $"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\""); Logger.Error?.Print(LogClass.Application, $"SDL3 open audio device initialization failed with error \"{SDL_GetError()}\"");
return 0; return null;
} }
bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels; bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels;
if (!isValid) if (!isValid)
{ {
Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid"); Logger.Error?.Print(LogClass.Application, "SDL3 open audio device is not valid");
SDL_CloseAudioDevice(device); SDL_DestroyAudioStream(device);
return 0; return null;
} }
return device; return device;
@@ -168,12 +166,12 @@ namespace Ryujinx.Audio.Backends.SDL2
{ {
if (disposing) if (disposing)
{ {
foreach (SDL2HardwareDeviceSession session in _sessions.Keys) foreach (SDL3HardwareDeviceSession session in _sessions.Keys)
{ {
session.Dispose(); session.Dispose();
} }
SDL2Driver.Instance.Dispose(); SDL3Driver.Instance.Dispose();
_pauseEvent.Dispose(); _pauseEvent.Dispose();
} }
@@ -6,36 +6,40 @@ using Ryujinx.Memory;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading; using System.Threading;
using SDL;
using static SDL.SDL3;
using System.Runtime.InteropServices;
using static SDL2.SDL; namespace Ryujinx.Audio.Backends.SDL3
namespace Ryujinx.Audio.Backends.SDL2
{ {
sealed class SDL2HardwareDeviceSession : HardwareDeviceSessionOutputBase unsafe sealed class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase
{ {
private readonly SDL2HardwareDeviceDriver _driver; private readonly SDL3HardwareDeviceDriver _driver;
private readonly ConcurrentQueue<SDL2AudioBuffer> _queuedBuffers; private readonly ConcurrentQueue<SDL3AudioBuffer> _queuedBuffers;
private readonly DynamicRingBuffer _ringBuffer; private readonly DynamicRingBuffer _ringBuffer;
private ulong _playedSampleCount; private ulong _playedSampleCount;
private readonly ManualResetEvent _updateRequiredEvent; private readonly ManualResetEvent _updateRequiredEvent;
private uint _outputStream; private SDL_AudioStream* _outputStream;
private bool _hasSetupError; private bool _hasSetupError;
private readonly SDL_AudioCallback _callbackDelegate; private readonly SDL_AudioStreamCallback _callbackDelegate;
private readonly int _bytesPerFrame; private readonly int _bytesPerFrame;
private uint _sampleCount; private uint _sampleCount;
private bool _started; private bool _started;
private float _volume; private float _volume;
private readonly ushort _nativeSampleFormat; private readonly SDL_AudioFormat _nativeSampleFormat;
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void SDL_AudioStreamCallback(nint session, SDL_AudioStream* stream, int stream_count, int device_count);
public SDL3HardwareDeviceSession(SDL3HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{ {
_driver = driver; _driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent(); _updateRequiredEvent = _driver.GetUpdateRequiredEvent();
_queuedBuffers = new ConcurrentQueue<SDL2AudioBuffer>(); _queuedBuffers = new ConcurrentQueue<SDL3AudioBuffer>();
_ringBuffer = new DynamicRingBuffer(); _ringBuffer = new DynamicRingBuffer();
_callbackDelegate = Update; _callbackDelegate = Update;
_bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount; _bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount;
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat); _nativeSampleFormat = SDL3HardwareDeviceDriver.GetSDL3Format(RequestedSampleFormat);
_sampleCount = uint.MaxValue; _sampleCount = uint.MaxValue;
_started = false; _started = false;
_volume = 1f; _volume = 1f;
@@ -44,45 +48,51 @@ namespace Ryujinx.Audio.Backends.SDL2
private void EnsureAudioStreamSetup(AudioBuffer buffer) private void EnsureAudioStreamSetup(AudioBuffer buffer)
{ {
uint bufferSampleCount = (uint)GetSampleCount(buffer); uint bufferSampleCount = (uint)GetSampleCount(buffer);
bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) || bool needAudioSetup = (_outputStream == null && !_hasSetupError) ||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount); (bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
if (needAudioSetup) if (needAudioSetup)
{ {
_sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount); _sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount);
uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate); SDL_AudioStream* newOutputStream = SDL3HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
_hasSetupError = newOutputStream == 0; _hasSetupError = newOutputStream == null;
if (!_hasSetupError) if (!_hasSetupError)
{ {
if (_outputStream != 0) if (_outputStream != null)
{ {
SDL_CloseAudioDevice(_outputStream); SDL_DestroyAudioStream(_outputStream);
} }
_outputStream = newOutputStream; _outputStream = newOutputStream;
SDL_PauseAudioDevice(_outputStream, _started ? 0 : 1); if (_started) {
SDL_ResumeAudioStreamDevice(_outputStream);
} else {
SDL_PauseAudioStreamDevice(_outputStream);
}
Logger.Info?.Print(LogClass.Audio, $"New audio stream setup with a target sample count of {_sampleCount}"); 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) private void Update(nint userdata, SDL_AudioStream* streamDevice, int additionalAmount, int totalAmmount)
{ {
Span<byte> streamSpan = new((void*)stream, streamLength); using SpanOwner<byte> stream = SpanOwner<byte>.Rent(additionalAmount);
Span<byte> streamSpan = stream.Span;
int maxFrameCount = (int)GetSampleCount(streamLength);
int maxFrameCount = (int)GetSampleCount(additionalAmount);
int bufferedFrames = _ringBuffer.Length / _bytesPerFrame; int bufferedFrames = _ringBuffer.Length / _bytesPerFrame;
int frameCount = Math.Min(bufferedFrames, maxFrameCount); int frameCount = Math.Min(bufferedFrames, maxFrameCount);
if (frameCount == 0) if (frameCount == 0)
{ {
// SDL2 left the responsibility to the user to clear the buffer. // SDL3 left the responsibility to the user to clear the buffer.
streamSpan.Clear(); streamSpan.Clear();
return; return;
@@ -94,15 +104,17 @@ namespace Ryujinx.Audio.Backends.SDL2
_ringBuffer.Read(samples, 0, samples.Length); _ringBuffer.Read(samples, 0, samples.Length);
fixed (byte* p = samples) // Zero the dest buffer
{ streamSpan.Clear();
nint pStreamSrc = (nint)p;
// Zero the dest buffer fixed (byte* pStreamDst = streamSpan) {
streamSpan.Clear(); fixed (byte* pStreamSrc = samples)
{
// Apply volume to written data // Apply volume to written data
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME)); SDL_MixAudio(pStreamDst, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, _driver.Volume * _volume);
SDL_PutAudioStreamData(streamDevice, (nint)pStreamDst, additionalAmount);
}
} }
ulong sampleCount = GetSampleCount(samples.Length); ulong sampleCount = GetSampleCount(samples.Length);
@@ -111,7 +123,7 @@ namespace Ryujinx.Audio.Backends.SDL2
bool needUpdate = false; bool needUpdate = false;
while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer)) while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL3AudioBuffer driverBuffer))
{ {
ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed); ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed);
ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount); ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount);
@@ -152,9 +164,9 @@ namespace Ryujinx.Audio.Backends.SDL2
{ {
EnsureAudioStreamSetup(buffer); EnsureAudioStreamSetup(buffer);
if (_outputStream != 0) if (_outputStream != null)
{ {
SDL2AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer)); SDL3AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer));
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
@@ -177,9 +189,9 @@ namespace Ryujinx.Audio.Backends.SDL2
{ {
if (!_started) if (!_started)
{ {
if (_outputStream != 0) if (_outputStream != null)
{ {
SDL_PauseAudioDevice(_outputStream, 0); SDL_ResumeAudioStreamDevice(_outputStream);
} }
_started = true; _started = true;
@@ -190,9 +202,9 @@ namespace Ryujinx.Audio.Backends.SDL2
{ {
if (_started) if (_started)
{ {
if (_outputStream != 0) if (_outputStream != null)
{ {
SDL_PauseAudioDevice(_outputStream, 1); SDL_PauseAudioStreamDevice(_outputStream);
} }
_started = false; _started = false;
@@ -203,7 +215,7 @@ namespace Ryujinx.Audio.Backends.SDL2
public override bool WasBufferFullyConsumed(AudioBuffer buffer) public override bool WasBufferFullyConsumed(AudioBuffer buffer)
{ {
if (!_queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer)) if (!_queuedBuffers.TryPeek(out SDL3AudioBuffer driverBuffer))
{ {
return true; return true;
} }
@@ -218,9 +230,9 @@ namespace Ryujinx.Audio.Backends.SDL2
PrepareToClose(); PrepareToClose();
Stop(); Stop();
if (_outputStream != 0) if (_outputStream != null)
{ {
SDL_CloseAudioDevice(_outputStream); SDL_DestroyAudioStream(_outputStream);
} }
} }
} }
@@ -7,6 +7,7 @@ namespace Ryujinx.Common.Configuration.Hid
{ {
Invalid, Invalid,
WindowKeyboard, WindowKeyboard,
GamepadSDL2, GamepadSDL2, //backcompat
GamepadSDL3,
} }
} }
@@ -58,7 +58,7 @@ namespace Ryujinx.Common.Configuration.Hid
return backendType switch return backendType switch
{ {
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardKeyboardInputConfig), InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardKeyboardInputConfig),
InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardControllerInputConfig), InputBackendType.GamepadSDL2 or InputBackendType.GamepadSDL3 => JsonSerializer.Deserialize(ref reader, _serializerContext.StandardControllerInputConfig),
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"), _ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
}; };
} }
@@ -70,7 +70,7 @@ namespace Ryujinx.Common.Configuration.Hid
case InputBackendType.WindowKeyboard: case InputBackendType.WindowKeyboard:
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, _serializerContext.StandardKeyboardInputConfig); JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, _serializerContext.StandardKeyboardInputConfig);
break; break;
case InputBackendType.GamepadSDL2: case InputBackendType.GamepadSDL2 or InputBackendType.GamepadSDL3:
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, _serializerContext.StandardControllerInputConfig); JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, _serializerContext.StandardControllerInputConfig);
break; break;
default: default:
-228
View File
@@ -1,228 +0,0 @@
using Ryujinx.Common.Logging;
using Ryujinx.SDL2.Common;
using System;
using System.Collections.Generic;
using System.Threading;
using static SDL2.SDL;
namespace Ryujinx.Input.SDL2
{
public class SDL2GamepadDriver : IGamepadDriver
{
private readonly Dictionary<int, string> _gamepadsInstanceIdsMapping;
private readonly List<string> _gamepadsIds;
private readonly Lock _lock = new();
public ReadOnlySpan<string> GamepadsIds
{
get
{
lock (_lock)
{
return _gamepadsIds.ToArray();
}
}
}
public string DriverName => "SDL2";
public event Action<string> OnGamepadConnected;
public event Action<string> OnGamepadDisconnected;
public SDL2GamepadDriver()
{
_gamepadsInstanceIdsMapping = new Dictionary<int, string>();
_gamepadsIds = [];
SDL2Driver.Instance.Initialize();
SDL2Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
SDL2Driver.Instance.OnJoyBatteryUpdated += HandleJoyBatteryUpdated;
// Add already connected gamepads
int numJoysticks = SDL_NumJoysticks();
for (int joystickIndex = 0; joystickIndex < numJoysticks; joystickIndex++)
{
HandleJoyStickConnected(joystickIndex, SDL_JoystickGetDeviceInstanceID(joystickIndex));
}
}
private string GenerateGamepadId(int joystickIndex)
{
Guid guid = SDL_JoystickGetDeviceGUID(joystickIndex);
// Add a unique identifier to the start of the GUID in case of duplicates.
if (guid == Guid.Empty)
{
return null;
}
// Remove the first 4 char of the guid (CRC part) to make it stable
string guidString = "0000" + guid.ToString().Substring(4);
string id;
lock (_lock)
{
int guidIndex = 0;
id = guidIndex + "-" + guidString;
while (_gamepadsIds.Contains(id))
{
id = (++guidIndex) + "-" + guidString;
}
}
return id;
}
private int GetJoystickIndexByGamepadId(string id)
{
lock (_lock)
{
return _gamepadsIds.IndexOf(id);
}
}
private void HandleJoyStickDisconnected(int joystickInstanceId)
{
bool joyConPairDisconnected = false;
if (_gamepadsInstanceIdsMapping.TryGetValue(joystickInstanceId, out string id))
{
_gamepadsInstanceIdsMapping.Remove(joystickInstanceId);
lock (_lock)
{
_gamepadsIds.Remove(id);
if (!SDL2JoyConPair.IsCombinable(_gamepadsIds))
{
_gamepadsIds.Remove(SDL2JoyConPair.Id);
joyConPairDisconnected = true;
}
}
}
OnGamepadDisconnected?.Invoke(id);
if (joyConPairDisconnected)
{
OnGamepadDisconnected?.Invoke(SDL2JoyConPair.Id);
}
}
private void HandleJoyStickConnected(int joystickDeviceId, int joystickInstanceId)
{
bool joyConPairConnected = false;
if (SDL_IsGameController(joystickDeviceId) == SDL_bool.SDL_TRUE)
{
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
{
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
// so it is rejected to avoid doubling the entries.
return;
}
string id = GenerateGamepadId(joystickDeviceId);
if (id == null)
{
return;
}
if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
{
lock (_lock)
{
if (joystickDeviceId <= _gamepadsIds.FindLastIndex(_ => true))
_gamepadsIds.Insert(joystickDeviceId, id);
else
_gamepadsIds.Add(id);
if (SDL2JoyConPair.IsCombinable(_gamepadsIds))
{
_gamepadsIds.Remove(SDL2JoyConPair.Id);
_gamepadsIds.Add(SDL2JoyConPair.Id);
joyConPairConnected = true;
}
}
OnGamepadConnected?.Invoke(id);
if (joyConPairConnected)
{
OnGamepadConnected?.Invoke(SDL2JoyConPair.Id);
}
}
}
}
private void HandleJoyBatteryUpdated(int joystickDeviceId, SDL_JoystickPowerLevel powerLevel)
{
Logger.Info?.Print(LogClass.Hid,
$"{SDL_GameControllerNameForIndex(joystickDeviceId)} power level: {powerLevel}");
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
SDL2Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected;
SDL2Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected;
// Simulate a full disconnect when disposing
foreach (string id in _gamepadsIds)
{
OnGamepadDisconnected?.Invoke(id);
}
lock (_lock)
{
_gamepadsIds.Clear();
}
SDL2Driver.Instance.Dispose();
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
public IGamepad GetGamepad(string id)
{
if (id == SDL2JoyConPair.Id)
{
lock (_lock)
{
return SDL2JoyConPair.GetGamepad(_gamepadsIds);
}
}
int joystickIndex = GetJoystickIndexByGamepadId(id);
if (joystickIndex == -1)
{
return null;
}
nint gamepadHandle = SDL_GameControllerOpen(joystickIndex);
if (gamepadHandle == nint.Zero)
{
return null;
}
if (SDL_GameControllerName(gamepadHandle).StartsWith(SDL2JoyCon.Prefix))
{
return new SDL2JoyCon(gamepadHandle, id);
}
return new SDL2Gamepad(gamepadHandle, id);
}
}
}
@@ -6,7 +6,7 @@
<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" /> <ProjectReference Include="..\Ryujinx.SDL3.Common\Ryujinx.SDL3.Common.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,59 +1,63 @@
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using SDL;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Threading; using System.Threading;
using static SDL2.SDL; using static SDL.SDL3;
namespace Ryujinx.Input.SDL2 namespace Ryujinx.Input.SDL3
{ {
class SDL2Gamepad : IGamepad public unsafe class SDL3Gamepad : IGamepad
{ {
private bool HasConfiguration => _configuration != null; private bool HasConfiguration => _configuration != null;
private record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From); private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From)
{
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound;
}
private StandardControllerInputConfig _configuration; private StandardControllerInputConfig _configuration;
private static readonly SDL_GameControllerButton[] _buttonsDriverMapping = private readonly SDL_GamepadButton[] _buttonsDriverMapping =
[ [
// Unbound, ignored. // Unbound, ignored.
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_STICK,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
// NOTE: The left and right trigger are axis, we handle those differently // NOTE: The left and right trigger are axis, we handle those differently
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_UP,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_DOWN,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_LEFT,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_BACK,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_GUIDE,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_MISC1, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_MISC1,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_TOUCHPAD, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_TOUCHPAD,
// Virtual buttons are invalid, ignored. // Virtual buttons are invalid, ignored.
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID,
]; ];
private readonly Lock _userMappingLock = new(); private readonly Lock _userMappingLock = new();
@@ -69,29 +73,43 @@ namespace Ryujinx.Input.SDL2
public GamepadFeaturesFlag Features { get; } public GamepadFeaturesFlag Features { get; }
private nint _gamepadHandle; private SDL_Gamepad* _gamepadHandle;
private float _triggerThreshold; private float _triggerThreshold;
public SDL2Gamepad(nint gamepadHandle, string driverId) public SDL3Gamepad(SDL_Gamepad* gamepadHandle, string driverId)
{ {
_gamepadHandle = gamepadHandle; _gamepadHandle = gamepadHandle;
_buttonsUserMapping = new List<ButtonMappingEntry>(20); _buttonsUserMapping = new List<ButtonMappingEntry>(20);
Name = SDL_GameControllerName(_gamepadHandle); Name = SDL_GetGamepadName(_gamepadHandle);
Id = driverId; Id = driverId;
Features = GetFeaturesFlag(); Features = GetFeaturesFlag();
_triggerThreshold = 0.0f; _triggerThreshold = 0.0f;
// Face button mapping
SDL_GamepadButton[] faceButtons = _buttonsDriverMapping[1..5];
foreach (SDL_GamepadButton btn in faceButtons) {
int mapId = SDL_GetGamepadButtonLabel(_gamepadHandle, btn) switch {
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_A or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_CROSS => 1,
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_B or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_CIRCLE => 2,
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_X or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_SQUARE => 3,
SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_Y or SDL_GamepadButtonLabel.SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE => 4,
_ => -1
};
if (mapId == -1) { continue; }
_buttonsDriverMapping[mapId] = btn;
}
// Enable motion tracking // Enable motion tracking
if ((Features & GamepadFeaturesFlag.Motion) != 0) if ((Features & GamepadFeaturesFlag.Motion) != 0)
{ {
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, SDL_bool.SDL_TRUE) != 0) if (!SDL_SetGamepadSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, true))
{ {
Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}."); Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}.");
} }
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, SDL_bool.SDL_TRUE) != 0) if (!SDL_SetGamepadSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, true))
{ {
Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}."); Logger.Error?.Print(LogClass.Hid, $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}.");
} }
@@ -102,40 +120,43 @@ namespace Ryujinx.Input.SDL2
{ {
GamepadFeaturesFlag result = GamepadFeaturesFlag.None; GamepadFeaturesFlag result = GamepadFeaturesFlag.None;
if (SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) == SDL_bool.SDL_TRUE && if (SDL_GamepadHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) &&
SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO) == SDL_bool.SDL_TRUE) SDL_GamepadHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO))
{ {
result |= GamepadFeaturesFlag.Motion; result |= GamepadFeaturesFlag.Motion;
} }
SDL_PropertiesID propID = SDL_GetGamepadProperties(_gamepadHandle);
int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100); SDL_LockProperties(propID);
if (SDL_GetBooleanProperty(propID, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, false))
if (error == 0)
{ {
result |= GamepadFeaturesFlag.Rumble; result |= GamepadFeaturesFlag.Rumble;
} }
SDL_UnlockProperties(propID);
SDL_DestroyProperties(propID);
return result; return result;
} }
public string Id { get; } public string Id { get; }
public string Name { get; } public string Name { get; }
public bool IsConnected => SDL_GameControllerGetAttached(_gamepadHandle) == SDL_bool.SDL_TRUE; public bool IsConnected => SDL_GamepadConnected(_gamepadHandle);
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing && _gamepadHandle != nint.Zero) if (disposing && _gamepadHandle != null)
{ {
SDL_GameControllerClose(_gamepadHandle); SDL_CloseGamepad(_gamepadHandle);
_gamepadHandle = nint.Zero; _gamepadHandle = null;
} }
} }
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this);
} }
public void SetTriggerThreshold(float triggerThreshold) public void SetTriggerThreshold(float triggerThreshold)
@@ -146,85 +167,62 @@ namespace Ryujinx.Input.SDL2
public void Rumble(float lowFrequency, float highFrequency, uint durationMs) public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
{ {
if ((Features & GamepadFeaturesFlag.Rumble) == 0) if ((Features & GamepadFeaturesFlag.Rumble) == 0)
{ return;
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
if (durationMs == uint.MaxValue) ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
{ ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) != 0)
{ if (durationMs == uint.MaxValue)
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); {
} if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY))
} Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
else if (durationMs > SDL_HAPTIC_INFINITY) }
{ else if (durationMs > SDL_HAPTIC_INFINITY)
Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}"); {
} Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}");
else }
{ else
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0) {
{ if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
}
}
} }
} }
public Vector3 GetMotionData(MotionInputId inputId) public Vector3 GetMotionData(MotionInputId inputId)
{ {
SDL_SensorType sensorType = SDL_SensorType.SDL_SENSOR_INVALID; SDL_SensorType sensorType = inputId switch
if (inputId == MotionInputId.Accelerometer)
{ {
sensorType = SDL_SensorType.SDL_SENSOR_ACCEL; MotionInputId.Accelerometer => SDL_SensorType.SDL_SENSOR_ACCEL,
} MotionInputId.Gyroscope => SDL_SensorType.SDL_SENSOR_GYRO,
else if (inputId == MotionInputId.Gyroscope) _ => SDL_SensorType.SDL_SENSOR_INVALID
{ };
sensorType = SDL_SensorType.SDL_SENSOR_GYRO;
}
if ((Features & GamepadFeaturesFlag.Motion) == 0 || sensorType is SDL_SensorType.SDL_SENSOR_INVALID) if (!Features.HasFlag(GamepadFeaturesFlag.Motion) || sensorType is SDL_SensorType.SDL_SENSOR_INVALID)
return Vector3.Zero; return Vector3.Zero;
const int ElementCount = 3; const int ElementCount = 3;
unsafe float[] values = new float[3];
{
float* values = stackalloc float[ElementCount];
int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (nint)values, ElementCount); fixed (float* pValues = &values[0]) {
if (result == 0) if (!SDL_GetGamepadSensorData(_gamepadHandle, sensorType, pValues, ElementCount))
return Vector3.Zero;
Vector3 value = new(values[0], values[1], values[2]);
return inputId switch
{ {
Vector3 value = new(values[0], values[1], values[2]); MotionInputId.Gyroscope => RadToDegree(value),
MotionInputId.Accelerometer => GsToMs2(value),
if (inputId == MotionInputId.Gyroscope) _ => value
{ };
return RadToDegree(value);
}
if (inputId == MotionInputId.Accelerometer)
{
return GsToMs2(value);
}
return value;
}
} }
return Vector3.Zero;
} }
private static Vector3 RadToDegree(Vector3 rad) private static Vector3 RadToDegree(Vector3 rad) => rad * (180 / MathF.PI);
{
return rad * (180 / MathF.PI);
}
private static Vector3 GsToMs2(Vector3 gs) private static Vector3 GsToMs2(Vector3 gs) => gs / SDL_STANDARD_GRAVITY;
{
return gs / SDL_STANDARD_GRAVITY;
}
public void SetConfiguration(InputConfig configuration) public void SetConfiguration(InputConfig configuration)
{ {
@@ -279,16 +277,13 @@ namespace Ryujinx.Input.SDL2
lock (_userMappingLock) lock (_userMappingLock)
{ {
if (_buttonsUserMapping.Count == 0) if (_buttonsUserMapping.Count == 0)
{
return rawState; return rawState;
}
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
foreach (ButtonMappingEntry entry in _buttonsUserMapping) foreach (ButtonMappingEntry entry in _buttonsUserMapping)
{ {
if (entry.From == GamepadButtonInputId.Unbound || entry.To == GamepadButtonInputId.Unbound) if (!entry.IsValid)
{
continue; continue;
}
// Do not touch state of button already pressed // Do not touch state of button already pressed
if (!result.IsPressed(entry.To)) if (!result.IsPressed(entry.To))
@@ -329,15 +324,14 @@ namespace Ryujinx.Input.SDL2
else else
return _configuration.RightJoyconStick; return _configuration.RightJoyconStick;
} }
return null; return null;
} }
public (float, float) GetStick(StickInputId inputId) public (float, float) GetStick(StickInputId inputId)
{ {
if (inputId == StickInputId.Unbound) if (inputId == StickInputId.Unbound)
{
return (0.0f, 0.0f); return (0.0f, 0.0f);
}
(short stickX, short stickY) = GetStickXY(inputId); (short stickX, short stickY) = GetStickXY(inputId);
@@ -346,7 +340,7 @@ namespace Ryujinx.Input.SDL2
if (HasConfiguration) if (HasConfiguration)
{ {
var joyconStickConfig = GetLogicalJoyStickConfig(inputId); JoyconConfigControllerStick<GamepadInputId, Common.Configuration.Hid.Controller.StickInputId> joyconStickConfig = GetLogicalJoyStickConfig(inputId);
if (joyconStickConfig != null) if (joyconStickConfig != null)
{ {
@@ -368,36 +362,35 @@ namespace Ryujinx.Input.SDL2
return (resultX, resultY); return (resultX, resultY);
} }
// ReSharper disable once InconsistentNaming
private (short, short) GetStickXY(StickInputId inputId) => private (short, short) GetStickXY(StickInputId inputId) =>
inputId switch inputId switch
{ {
StickInputId.Left => ( StickInputId.Left => (
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX), SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTX),
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY)), SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTY)),
StickInputId.Right => ( StickInputId.Right => (
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX), SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHTX),
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY)), SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHTY)),
_ => throw new NotSupportedException($"Unsupported stick {inputId}") _ => throw new NotSupportedException($"Unsupported stick {inputId}")
}; };
public bool IsPressed(GamepadButtonInputId inputId) public bool IsPressed(GamepadButtonInputId inputId)
{ {
if (inputId == GamepadButtonInputId.LeftTrigger) switch (inputId)
{ {
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > _triggerThreshold; case GamepadButtonInputId.LeftTrigger:
return ConvertRawStickValue(SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFT_TRIGGER)) > _triggerThreshold;
case GamepadButtonInputId.RightTrigger:
return ConvertRawStickValue(SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)) > _triggerThreshold;
} }
if (inputId == GamepadButtonInputId.RightTrigger) if (_buttonsDriverMapping[(int)inputId] == SDL_GamepadButton.SDL_GAMEPAD_BUTTON_INVALID)
{
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > _triggerThreshold;
}
if (_buttonsDriverMapping[(int)inputId] == SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_INVALID)
{ {
return false; return false;
} }
return SDL_GameControllerGetButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]) == 1; return SDL_GetGamepadButton(_gamepadHandle, _buttonsDriverMapping[(int)inputId]);
} }
} }
} }
+254
View File
@@ -0,0 +1,254 @@
using Ryujinx.Common.Logging;
using Ryujinx.SDL3.Common;
using System;
using System.Collections.Generic;
using System.Threading;
using SDL;
using System.Linq;
using static SDL.SDL3;
namespace Ryujinx.Input.SDL3
{
public unsafe class SDL3GamepadDriver : IGamepadDriver
{
private readonly Dictionary<SDL_JoystickID, string> _gamepadsInstanceIdsMapping;
private readonly Dictionary<SDL_JoystickID, string> _gamepadsIds;
private readonly Lock _lock = new();
public ReadOnlySpan<string> GamepadsIds
{
get
{
lock (_lock)
{
return _gamepadsIds.Values.ToArray();
}
}
}
public string DriverName => "SDL3";
public event Action<string> OnGamepadConnected;
public event Action<string> OnGamepadDisconnected;
public SDL3GamepadDriver()
{
_gamepadsInstanceIdsMapping = new Dictionary<SDL_JoystickID, string>();
_gamepadsIds = [];
SDL3Driver.Instance.Initialize();
SDL3Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
SDL3Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
SDL3Driver.Instance.OnJoyBatteryUpdated += HandleJoyBatteryUpdated;
// Add already connected gamepads
int joystickCount = 0;
SDL_JoystickID* pJoystickInstanceIds = SDL_GetJoysticks(&joystickCount);
for (int i = 0; i < joystickCount; i++)
{
HandleJoyStickConnected(pJoystickInstanceIds[i]);
}
}
private static string SDLGuidToString(SDL_GUID guid)
{
string map = "0123456789abcdef";
char[] guidBytes = new char[33];
for (int i = 0; i < 16; i++) {
byte c = guid.data[i];
guidBytes[i * 2] = map[c >> 4];
guidBytes[(i * 2) + 1] = map[c & 0x0f];
}
string strGuid = new(guidBytes);
return $"{strGuid[4..6]}{strGuid[6..8]}{strGuid[2..4]}{strGuid[0..2]}-{strGuid[10..12]}{strGuid[8..10]}-{strGuid[12..16]}-{strGuid[16..20]}-{strGuid[20..32]}";
}
private string GenerateGamepadId(SDL_JoystickID joystickInstanceId)
{
SDL_GUID sdlGuid = SDL_GetJoystickGUIDForID(joystickInstanceId);
string guidBytes = SDLGuidToString(sdlGuid);
Guid guid = Guid.Parse(guidBytes);
// Add a unique identifier to the start of the GUID in case of duplicates.
if (guid == Guid.Empty)
{
return null;
}
// Remove the first 4 char of the guid (CRC part) to make it stable
string guidString = $"0000{guid.ToString()[4..]}";
string id;
lock (_lock)
{
int guidIndex = 0;
id = guidIndex + "-" + guidString;
while (_gamepadsIds.ContainsValue(id))
{
id = (++guidIndex) + "-" + guidString;
}
}
return id;
}
private void HandleJoyStickDisconnected(SDL_JoystickID joystickInstanceId)
{
bool joyConPairDisconnected = false;
if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
return;
lock (_lock)
{
_gamepadsIds.Remove(joystickInstanceId);
if (!SDL3JoyConPair.IsCombinable(_gamepadsIds))
{
_gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id));
joyConPairDisconnected = true;
}
}
OnGamepadDisconnected?.Invoke(id);
if (joyConPairDisconnected)
{
OnGamepadDisconnected?.Invoke(SDL3JoyConPair.Id);
}
}
private void HandleJoyStickConnected(SDL_JoystickID joystickInstanceId)
{
bool joyConPairConnected = false;
if (SDL_IsGamepad(joystickInstanceId))
{
if (_gamepadsInstanceIdsMapping.ContainsKey(joystickInstanceId))
{
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
// so it is rejected to avoid doubling the entries.
return;
}
string id = GenerateGamepadId(joystickInstanceId);
if (id == null)
{
return;
}
if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
{
lock (_lock)
{
_gamepadsIds.Add(joystickInstanceId, id);
if (SDL3JoyConPair.IsCombinable(_gamepadsIds))
{
// TODO - It appears that you can only have one joy con pair connected at a time?
// This was also the behavior before SDL3
_gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id));
uint fakeInstanceID = uint.MaxValue;
while (!_gamepadsIds.TryAdd((SDL_JoystickID)fakeInstanceID, SDL3JoyConPair.Id))
{
fakeInstanceID--;
}
joyConPairConnected = true;
}
}
OnGamepadConnected?.Invoke(id);
if (joyConPairConnected)
{
OnGamepadConnected?.Invoke(SDL3JoyConPair.Id);
}
}
}
}
private void HandleJoyBatteryUpdated(SDL_JoystickID joystickInstanceId, SDL_PowerState powerLevel)
{
Logger.Info?.Print(LogClass.Hid,
$"{SDL_GetGamepadNameForID(joystickInstanceId)} power level: {powerLevel}");
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
SDL3Driver.Instance.OnJoyStickConnected -= HandleJoyStickConnected;
SDL3Driver.Instance.OnJoystickDisconnected -= HandleJoyStickDisconnected;
// Simulate a full disconnect when disposing
foreach (var gamepad in _gamepadsIds)
{
OnGamepadDisconnected?.Invoke(gamepad.Value);
}
lock (_lock)
{
_gamepadsIds.Clear();
}
SDL3Driver.Instance.Dispose();
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
public SDL_JoystickID GetInstanceIdFromId(string id) {
return _gamepadsInstanceIdsMapping.Where(e => e.Value == id).FirstOrDefault().Key;
}
public IGamepad GetGamepad(string id)
{
if (id == SDL3JoyConPair.Id)
{
lock (_lock)
{
return SDL3JoyConPair.GetGamepad(_gamepadsIds);
}
}
SDL_JoystickID instanceId = GetInstanceIdFromId(id);
SDL_Gamepad* gamepadHandle = SDL_OpenGamepad(instanceId);
if (gamepadHandle == null)
{
return null;
}
if (SDL_GetGamepadName(gamepadHandle).StartsWith(SDL3JoyCon.Prefix))
{
return new SDL3JoyCon(gamepadHandle, id);
}
return new SDL3Gamepad(gamepadHandle, id);
}
public IEnumerable<IGamepad> GetGamepads()
{
lock (_gamepadsIds)
{
foreach (var gamepad in _gamepadsIds)
{
yield return GetGamepad(gamepad.Value);
}
}
}
}
}
@@ -1,15 +1,16 @@
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using SDL;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Threading; using System.Threading;
using static SDL2.SDL; using static SDL.SDL3;
namespace Ryujinx.Input.SDL2 namespace Ryujinx.Input.SDL3
{ {
internal class SDL2JoyCon : IGamepad internal unsafe class SDL3JoyCon : IGamepad
{ {
private bool HasConfiguration => _configuration != null; private bool HasConfiguration => _configuration != null;
@@ -20,34 +21,34 @@ namespace Ryujinx.Input.SDL2
private StandardControllerInputConfig _configuration; private StandardControllerInputConfig _configuration;
private readonly Dictionary<GamepadButtonInputId,SDL_GameControllerButton> _leftButtonsDriverMapping = new() private readonly Dictionary<GamepadButtonInputId,SDL_GamepadButton> _leftButtonsDriverMapping = new()
{ {
{ GamepadButtonInputId.LeftStick , SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK }, { GamepadButtonInputId.LeftStick , SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK },
{GamepadButtonInputId.DpadUp ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y}, {GamepadButtonInputId.DpadUp ,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
{GamepadButtonInputId.DpadDown ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A}, {GamepadButtonInputId.DpadDown ,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
{GamepadButtonInputId.DpadLeft ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B}, {GamepadButtonInputId.DpadLeft ,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
{GamepadButtonInputId.DpadRight ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X}, {GamepadButtonInputId.DpadRight ,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
{GamepadButtonInputId.Minus ,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START}, {GamepadButtonInputId.Minus ,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START},
{GamepadButtonInputId.LeftShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE2}, {GamepadButtonInputId.LeftShoulder,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1},
{GamepadButtonInputId.LeftTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE4}, {GamepadButtonInputId.LeftTrigger,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
{GamepadButtonInputId.SingleRightTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, {GamepadButtonInputId.SingleRightTrigger0,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER},
{GamepadButtonInputId.SingleLeftTrigger0,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, {GamepadButtonInputId.SingleLeftTrigger0,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER},
}; };
private readonly Dictionary<GamepadButtonInputId,SDL_GameControllerButton> _rightButtonsDriverMapping = new() private readonly Dictionary<GamepadButtonInputId,SDL_GamepadButton> _rightButtonsDriverMapping = new()
{ {
{GamepadButtonInputId.RightStick,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK}, {GamepadButtonInputId.RightStick,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK},
{GamepadButtonInputId.A,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B}, {GamepadButtonInputId.A,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
{GamepadButtonInputId.B,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y}, {GamepadButtonInputId.B,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
{GamepadButtonInputId.X,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A}, {GamepadButtonInputId.X,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
{GamepadButtonInputId.Y,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X}, {GamepadButtonInputId.Y,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
{GamepadButtonInputId.Plus,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START}, {GamepadButtonInputId.Plus,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START},
{GamepadButtonInputId.RightShoulder,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE1}, {GamepadButtonInputId.RightShoulder,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
{GamepadButtonInputId.RightTrigger,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_PADDLE3}, {GamepadButtonInputId.RightTrigger,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
{GamepadButtonInputId.SingleRightTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, {GamepadButtonInputId.SingleRightTrigger1,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER},
{GamepadButtonInputId.SingleLeftTrigger1,SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER} {GamepadButtonInputId.SingleLeftTrigger1,SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_SHOULDER}
}; };
private readonly Dictionary<GamepadButtonInputId, SDL_GameControllerButton> _buttonsDriverMapping; private readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _buttonsDriverMapping;
private readonly Lock _userMappingLock = new(); private readonly Lock _userMappingLock = new();
private readonly List<ButtonMappingEntry> _buttonsUserMapping; private readonly List<ButtonMappingEntry> _buttonsUserMapping;
@@ -59,7 +60,7 @@ namespace Ryujinx.Input.SDL2
public GamepadFeaturesFlag Features { get; } public GamepadFeaturesFlag Features { get; }
private nint _gamepadHandle; private SDL_Gamepad* _gamepadHandle;
private enum JoyConType private enum JoyConType
{ {
@@ -72,27 +73,25 @@ namespace Ryujinx.Input.SDL2
private readonly JoyConType _joyConType; private readonly JoyConType _joyConType;
public SDL2JoyCon(nint gamepadHandle, string driverId) public SDL3JoyCon(SDL_Gamepad* gamepadHandle, string driverId)
{ {
_gamepadHandle = gamepadHandle; _gamepadHandle = gamepadHandle;
_buttonsUserMapping = new List<ButtonMappingEntry>(10); _buttonsUserMapping = new List<ButtonMappingEntry>(10);
Name = SDL_GameControllerName(_gamepadHandle); Name = SDL_GetGamepadName(_gamepadHandle);
Id = driverId; Id = driverId;
Features = GetFeaturesFlag(); Features = GetFeaturesFlag();
// Enable motion tracking // Enable motion tracking
if ((Features & GamepadFeaturesFlag.Motion) != 0) if ((Features & GamepadFeaturesFlag.Motion) != 0)
{ {
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, if (!SDL_SetGamepadSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL, true))
SDL_bool.SDL_TRUE) != 0)
{ {
Logger.Error?.Print(LogClass.Hid, Logger.Error?.Print(LogClass.Hid,
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}."); $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_ACCEL}.");
} }
if (SDL_GameControllerSetSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, if (!SDL_SetGamepadSensorEnabled(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO, true))
SDL_bool.SDL_TRUE) != 0)
{ {
Logger.Error?.Print(LogClass.Hid, Logger.Error?.Print(LogClass.Hid,
$"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}."); $"Could not enable data reporting for SensorType {SDL_SensorType.SDL_SENSOR_GYRO}.");
@@ -120,15 +119,13 @@ namespace Ryujinx.Input.SDL2
{ {
GamepadFeaturesFlag result = GamepadFeaturesFlag.None; GamepadFeaturesFlag result = GamepadFeaturesFlag.None;
if (SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) == SDL_bool.SDL_TRUE && if (SDL_GamepadHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_ACCEL) &&
SDL_GameControllerHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO) == SDL_bool.SDL_TRUE) SDL_GamepadHasSensor(_gamepadHandle, SDL_SensorType.SDL_SENSOR_GYRO))
{ {
result |= GamepadFeaturesFlag.Motion; result |= GamepadFeaturesFlag.Motion;
} }
int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100); if (SDL_RumbleGamepad(_gamepadHandle, 0, 0, 100))
if (error == 0)
{ {
result |= GamepadFeaturesFlag.Rumble; result |= GamepadFeaturesFlag.Rumble;
} }
@@ -138,15 +135,15 @@ namespace Ryujinx.Input.SDL2
public string Id { get; } public string Id { get; }
public string Name { get; } public string Name { get; }
public bool IsConnected => SDL_GameControllerGetAttached(_gamepadHandle) == SDL_bool.SDL_TRUE; public bool IsConnected => SDL_GamepadConnected(_gamepadHandle);
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing && _gamepadHandle != nint.Zero) if (disposing && _gamepadHandle != null)
{ {
SDL_GameControllerClose(_gamepadHandle); SDL_CloseGamepad(_gamepadHandle);
_gamepadHandle = nint.Zero; _gamepadHandle = null;
} }
} }
@@ -171,8 +168,7 @@ namespace Ryujinx.Input.SDL2
if (durationMs == uint.MaxValue) if (durationMs == uint.MaxValue)
{ {
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) != if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY))
0)
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
} }
else if (durationMs > SDL_HAPTIC_INFINITY) else if (durationMs > SDL_HAPTIC_INFINITY)
@@ -181,7 +177,7 @@ namespace Ryujinx.Input.SDL2
} }
else else
{ {
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0) if (!SDL_RumbleGamepad(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs))
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller."); Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
} }
} }
@@ -200,13 +196,10 @@ namespace Ryujinx.Input.SDL2
const int ElementCount = 3; const int ElementCount = 3;
unsafe float[] values = new float[3];
{
float* values = stackalloc float[ElementCount];
int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (nint)values, ElementCount); fixed (float* pValues = &values[0]) {
if (!SDL_GetGamepadSensorData(_gamepadHandle, sensorType, pValues, ElementCount))
if (result != 0)
return Vector3.Zero; return Vector3.Zero;
Vector3 value = _joyConType switch Vector3 value = _joyConType switch
@@ -276,10 +269,6 @@ namespace Ryujinx.Input.SDL2
} }
} }
public void SetLed(uint packedRgb)
{
}
public GamepadStateSnapshot GetStateSnapshot() public GamepadStateSnapshot GetStateSnapshot()
{ {
return IGamepad.GetStateSnapshot(this); return IGamepad.GetStateSnapshot(this);
@@ -394,18 +383,18 @@ namespace Ryujinx.Input.SDL2
private (short, short) GetStickXY() private (short, short) GetStickXY()
{ {
return ( return (
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX), SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTX),
SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY)); SDL_GetGamepadAxis(_gamepadHandle, SDL_GamepadAxis.SDL_GAMEPAD_AXIS_LEFTY));
} }
public bool IsPressed(GamepadButtonInputId inputId) public bool IsPressed(GamepadButtonInputId inputId)
{ {
if (!_buttonsDriverMapping.TryGetValue(inputId, out var button)) if (!_buttonsDriverMapping.TryGetValue(inputId, out SDL_GamepadButton button))
{ {
return false; return false;
} }
return SDL_GameControllerGetButton(_gamepadHandle, button) == 1; return SDL_GetGamepadButton(_gamepadHandle, button);
} }
} }
} }
@@ -2,11 +2,12 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using static SDL2.SDL; using SDL;
using static SDL.SDL3;
namespace Ryujinx.Input.SDL2 namespace Ryujinx.Input.SDL3
{ {
internal class SDL2JoyConPair(IGamepad left, IGamepad right) : IGamepad internal class SDL3JoyConPair(IGamepad left, IGamepad right) : IGamepad
{ {
public GamepadFeaturesFlag Features => (left?.Features ?? GamepadFeaturesFlag.None) | public GamepadFeaturesFlag Features => (left?.Features ?? GamepadFeaturesFlag.None) |
(right?.Features ?? GamepadFeaturesFlag.None); (right?.Features ?? GamepadFeaturesFlag.None);
@@ -85,51 +86,50 @@ namespace Ryujinx.Input.SDL2
right.SetConfiguration(configuration); right.SetConfiguration(configuration);
} }
public void SetLed(uint packedRgb)
{
}
public void SetTriggerThreshold(float triggerThreshold) public void SetTriggerThreshold(float triggerThreshold)
{ {
left.SetTriggerThreshold(triggerThreshold); left.SetTriggerThreshold(triggerThreshold);
right.SetTriggerThreshold(triggerThreshold); right.SetTriggerThreshold(triggerThreshold);
} }
public static bool IsCombinable(List<string> gamepadsIds) public static bool IsCombinable(Dictionary<SDL_JoystickID, string> gamepadsIds)
{ {
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds); (int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
return leftIndex >= 0 && rightIndex >= 0; return leftIndex >= 0 && rightIndex >= 0;
} }
private static (int leftIndex, int rightIndex) DetectJoyConPair(List<string> gamepadsIds) private static (int leftIndex, int rightIndex) DetectJoyConPair(Dictionary<SDL_JoystickID, string> gamepadsIds)
{ {
var gamepadNames = gamepadsIds.Where(gamepadId => gamepadId != Id) Dictionary<string, SDL_JoystickID> gamepadNames = gamepadsIds
.Select((_, index) => SDL_GameControllerNameForIndex(index)).ToList(); .Where(gamepadId => gamepadId.Value != Id && SDL_GetGamepadNameForID(gamepadId.Key) is SDL3JoyCon.LeftName or SDL3JoyCon.RightName)
int leftIndex = gamepadNames.IndexOf(SDL2JoyCon.LeftName); .Select(gamepad => (SDL_GetGamepadNameForID(gamepad.Key), gamepad.Key))
int rightIndex = gamepadNames.IndexOf(SDL2JoyCon.RightName); .ToDictionary();
SDL_JoystickID idx;
int leftIndex = gamepadNames.TryGetValue(SDL3JoyCon.LeftName, out idx) ? (int)idx : -1;
int rightIndex = gamepadNames.TryGetValue(SDL3JoyCon.RightName, out idx) ? (int)idx : -1;
return (leftIndex, rightIndex); return (leftIndex, rightIndex);
} }
public static IGamepad GetGamepad(List<string> gamepadsIds) public unsafe static IGamepad GetGamepad(Dictionary<SDL_JoystickID, string> gamepadsIds)
{ {
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds); (int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
if (leftIndex == -1 || rightIndex == -1)
if (leftIndex <= 0 || rightIndex <= 0)
{ {
return null; return null;
} }
nint leftGamepadHandle = SDL_GameControllerOpen(leftIndex); SDL_Gamepad* leftGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)leftIndex);
nint rightGamepadHandle = SDL_GameControllerOpen(rightIndex); SDL_Gamepad* rightGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)rightIndex);
if (leftGamepadHandle == nint.Zero || rightGamepadHandle == nint.Zero) if (leftGamepadHandle == null || rightGamepadHandle == null)
{ {
return null; return null;
} }
return new SDL3JoyConPair(new SDL3JoyCon(leftGamepadHandle, gamepadsIds[(SDL_JoystickID)leftIndex]),
return new SDL2JoyConPair(new SDL2JoyCon(leftGamepadHandle, gamepadsIds[leftIndex]), new SDL3JoyCon(rightGamepadHandle, gamepadsIds[(SDL_JoystickID)rightIndex]));
new SDL2JoyCon(rightGamepadHandle, gamepadsIds[rightIndex]));
} }
} }
} }
@@ -1,17 +1,18 @@
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Configuration.Hid.Keyboard;
using SDL;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using static SDL2.SDL; using static SDL.SDL3;
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key; using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Input.SDL2 namespace Ryujinx.Input.SDL3
{ {
class SDL2Keyboard : IKeyboard class SDL3Keyboard : IKeyboard
{ {
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From) private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From)
{ {
@@ -21,7 +22,7 @@ namespace Ryujinx.Input.SDL2
private readonly Lock _userMappingLock = new(); private readonly Lock _userMappingLock = new();
#pragma warning disable IDE0052 // Remove unread private member #pragma warning disable IDE0052 // Remove unread private member
private readonly SDL2KeyboardDriver _driver; private readonly SDL3KeyboardDriver _driver;
#pragma warning restore IDE0052 #pragma warning restore IDE0052
private StandardKeyboardInputConfig _configuration; private StandardKeyboardInputConfig _configuration;
private readonly List<ButtonMappingEntry> _buttonsUserMapping; private readonly List<ButtonMappingEntry> _buttonsUserMapping;
@@ -115,32 +116,32 @@ namespace Ryujinx.Input.SDL2
SDL_Keycode.SDLK_KP_PLUS, SDL_Keycode.SDLK_KP_PLUS,
SDL_Keycode.SDLK_KP_DECIMAL, SDL_Keycode.SDLK_KP_DECIMAL,
SDL_Keycode.SDLK_KP_ENTER, SDL_Keycode.SDLK_KP_ENTER,
SDL_Keycode.SDLK_a, SDL_Keycode.SDLK_A,
SDL_Keycode.SDLK_b, SDL_Keycode.SDLK_B,
SDL_Keycode.SDLK_c, SDL_Keycode.SDLK_C,
SDL_Keycode.SDLK_d, SDL_Keycode.SDLK_D,
SDL_Keycode.SDLK_e, SDL_Keycode.SDLK_E,
SDL_Keycode.SDLK_f, SDL_Keycode.SDLK_F,
SDL_Keycode.SDLK_g, SDL_Keycode.SDLK_G,
SDL_Keycode.SDLK_h, SDL_Keycode.SDLK_H,
SDL_Keycode.SDLK_i, SDL_Keycode.SDLK_I,
SDL_Keycode.SDLK_j, SDL_Keycode.SDLK_J,
SDL_Keycode.SDLK_k, SDL_Keycode.SDLK_K,
SDL_Keycode.SDLK_l, SDL_Keycode.SDLK_L,
SDL_Keycode.SDLK_m, SDL_Keycode.SDLK_M,
SDL_Keycode.SDLK_n, SDL_Keycode.SDLK_N,
SDL_Keycode.SDLK_o, SDL_Keycode.SDLK_O,
SDL_Keycode.SDLK_p, SDL_Keycode.SDLK_P,
SDL_Keycode.SDLK_q, SDL_Keycode.SDLK_Q,
SDL_Keycode.SDLK_r, SDL_Keycode.SDLK_R,
SDL_Keycode.SDLK_s, SDL_Keycode.SDLK_S,
SDL_Keycode.SDLK_t, SDL_Keycode.SDLK_T,
SDL_Keycode.SDLK_u, SDL_Keycode.SDLK_U,
SDL_Keycode.SDLK_v, SDL_Keycode.SDLK_V,
SDL_Keycode.SDLK_w, SDL_Keycode.SDLK_W,
SDL_Keycode.SDLK_x, SDL_Keycode.SDLK_X,
SDL_Keycode.SDLK_y, SDL_Keycode.SDLK_Y,
SDL_Keycode.SDLK_z, SDL_Keycode.SDLK_Z,
SDL_Keycode.SDLK_0, SDL_Keycode.SDLK_0,
SDL_Keycode.SDLK_1, SDL_Keycode.SDLK_1,
SDL_Keycode.SDLK_2, SDL_Keycode.SDLK_2,
@@ -151,14 +152,14 @@ namespace Ryujinx.Input.SDL2
SDL_Keycode.SDLK_7, SDL_Keycode.SDLK_7,
SDL_Keycode.SDLK_8, SDL_Keycode.SDLK_8,
SDL_Keycode.SDLK_9, SDL_Keycode.SDLK_9,
SDL_Keycode.SDLK_BACKQUOTE, SDL_Keycode.SDLK_GRAVE,
SDL_Keycode.SDLK_BACKQUOTE, SDL_Keycode.SDLK_GRAVE,
SDL_Keycode.SDLK_MINUS, SDL_Keycode.SDLK_MINUS,
SDL_Keycode.SDLK_PLUS, SDL_Keycode.SDLK_PLUS,
SDL_Keycode.SDLK_LEFTBRACKET, SDL_Keycode.SDLK_LEFTBRACKET,
SDL_Keycode.SDLK_RIGHTBRACKET, SDL_Keycode.SDLK_RIGHTBRACKET,
SDL_Keycode.SDLK_SEMICOLON, SDL_Keycode.SDLK_SEMICOLON,
SDL_Keycode.SDLK_QUOTE, SDL_Keycode.SDLK_APOSTROPHE,
SDL_Keycode.SDLK_COMMA, SDL_Keycode.SDLK_COMMA,
SDL_Keycode.SDLK_PERIOD, SDL_Keycode.SDLK_PERIOD,
SDL_Keycode.SDLK_SLASH, SDL_Keycode.SDLK_SLASH,
@@ -168,7 +169,7 @@ namespace Ryujinx.Input.SDL2
SDL_Keycode.SDLK_0 SDL_Keycode.SDLK_0
]; ];
public SDL2Keyboard(SDL2KeyboardDriver driver, string id, string name) public SDL3Keyboard(SDL3KeyboardDriver driver, string id, string name)
{ {
_driver = driver; _driver = driver;
Id = id; Id = id;
@@ -192,55 +193,49 @@ namespace Ryujinx.Input.SDL2
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ToSDL2Scancode(Key key) private unsafe static int ToSDL3Scancode(Key key)
{ {
if (key >= Key.Unknown && key <= Key.Menu) if (key >= Key.Unknown && key <= Key.Menu)
{ {
return -1; return -1;
} }
return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key]); return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key], null);
} }
private static SDL_Keymod GetKeyboardModifierMask(Key key) private static SDL_Keymod GetKeyboardModifierMask(Key key)
{ {
return key switch return key switch
{ {
Key.ShiftLeft => SDL_Keymod.KMOD_LSHIFT, Key.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT,
Key.ShiftRight => SDL_Keymod.KMOD_RSHIFT, Key.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT,
Key.ControlLeft => SDL_Keymod.KMOD_LCTRL, Key.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL,
Key.ControlRight => SDL_Keymod.KMOD_RCTRL, Key.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL,
Key.AltLeft => SDL_Keymod.KMOD_LALT, Key.AltLeft => SDL_Keymod.SDL_KMOD_LALT,
Key.AltRight => SDL_Keymod.KMOD_RALT, Key.AltRight => SDL_Keymod.SDL_KMOD_RALT,
Key.WinLeft => SDL_Keymod.KMOD_LGUI, Key.WinLeft => SDL_Keymod.SDL_KMOD_LGUI,
Key.WinRight => SDL_Keymod.KMOD_RGUI, Key.WinRight => SDL_Keymod.SDL_KMOD_RGUI,
// NOTE: Menu key isn't supported by SDL2. // NOTE: Menu key isn't supported by SDL3.
_ => SDL_Keymod.KMOD_NONE, _ => SDL_Keymod.SDL_KMOD_NONE,
}; };
} }
public KeyboardStateSnapshot GetKeyboardStateSnapshot() public unsafe KeyboardStateSnapshot GetKeyboardStateSnapshot()
{ {
ReadOnlySpan<byte> rawKeyboardState;
SDL_Keymod rawKeyboardModifierState = SDL_GetModState(); SDL_Keymod rawKeyboardModifierState = SDL_GetModState();
unsafe SDLBool* rawKeyboardState = SDL_GetKeyboardState(null);
{
nint statePtr = SDL_GetKeyboardState(out int numKeys);
rawKeyboardState = new ReadOnlySpan<byte>((byte*)statePtr, numKeys);
}
bool[] keysState = new bool[(int)Key.Count]; bool[] keysState = new bool[(int)Key.Count];
for (Key key = 0; key < Key.Count; key++) for (Key key = 0; key < Key.Count; key++)
{ {
int index = ToSDL2Scancode(key); int index = ToSDL3Scancode(key);
if (index == -1) if (index == -1)
{ {
SDL_Keymod modifierMask = GetKeyboardModifierMask(key); SDL_Keymod modifierMask = GetKeyboardModifierMask(key);
if (modifierMask == SDL_Keymod.KMOD_NONE) if (modifierMask == SDL_Keymod.SDL_KMOD_NONE)
{ {
continue; continue;
} }
@@ -249,7 +244,7 @@ namespace Ryujinx.Input.SDL2
} }
else else
{ {
keysState[(int)key] = rawKeyboardState[index] == 1; keysState[(int)key] = rawKeyboardState[index];
} }
} }
@@ -3,17 +3,17 @@ using System;
using System.Drawing; using System.Drawing;
using System.Numerics; using System.Numerics;
namespace Ryujinx.Input.SDL2 namespace Ryujinx.Input.SDL3
{ {
class SDL2Mouse : IMouse class SDL3Mouse : IMouse
{ {
private SDL2MouseDriver _driver; private SDL3MouseDriver _driver;
public GamepadFeaturesFlag Features => throw new NotImplementedException(); public GamepadFeaturesFlag Features => throw new NotImplementedException();
public string Id => "0"; public string Id => "0";
public string Name => "SDL2Mouse"; public string Name => "SDL3Mouse";
public bool IsConnected => true; public bool IsConnected => true;
@@ -21,7 +21,7 @@ namespace Ryujinx.Input.SDL2
Size IMouse.ClientSize => _driver.GetClientSize(); Size IMouse.ClientSize => _driver.GetClientSize();
public SDL2Mouse(SDL2MouseDriver driver) public SDL3Mouse(SDL3MouseDriver driver)
{ {
_driver = driver; _driver = driver;
} }
@@ -1,15 +1,17 @@
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using SDL;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using static SDL2.SDL; using static SDL.SDL3;
namespace Ryujinx.Input.SDL2 namespace Ryujinx.Input.SDL3
{ {
public class SDL2MouseDriver : IGamepadDriver public class SDL3MouseDriver : IGamepadDriver
{ {
private const int CursorHideIdleTime = 5; // seconds private const int CursorHideIdleTime = 5; // seconds
@@ -24,14 +26,14 @@ namespace Ryujinx.Input.SDL2
public Vector2 Scroll { get; private set; } public Vector2 Scroll { get; private set; }
public Size ClientSize; public Size ClientSize;
public SDL2MouseDriver(HideCursorMode hideCursorMode) public SDL3MouseDriver(HideCursorMode hideCursorMode)
{ {
PressedButtons = new bool[(int)MouseButton.Count]; PressedButtons = new bool[(int)MouseButton.Count];
_hideCursorMode = hideCursorMode; _hideCursorMode = hideCursorMode;
if (_hideCursorMode == HideCursorMode.Always) if (_hideCursorMode == HideCursorMode.Always)
{ {
if (SDL_ShowCursor(SDL_DISABLE) != SDL_DISABLE) if (!SDL_HideCursor())
{ {
Logger.Error?.PrintMsg(LogClass.Application, "Failed to disable the cursor."); Logger.Error?.PrintMsg(LogClass.Application, "Failed to disable the cursor.");
} }
@@ -43,14 +45,16 @@ namespace Ryujinx.Input.SDL2
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static MouseButton DriverButtonToMouseButton(uint rawButton) private static MouseButton DriverButtonToMouseButton(uint rawButton)
{ {
Debug.Assert(rawButton > 0 && rawButton <= (int)MouseButton.Count); Debug.Assert(rawButton is > 0 and <= (int)MouseButton.Count);
return (MouseButton)(rawButton - 1); return (MouseButton)(rawButton - 1);
} }
public void UpdatePosition() public unsafe void UpdatePosition()
{ {
_ = SDL_GetMouseState(out int posX, out int posY); float posX = 0;
float posY = 0;
_ = SDL_GetMouseState(&posX, &posY);
Vector2 position = new(posX, posY); Vector2 position = new(posX, posY);
if (CurrentPosition != position) if (CurrentPosition != position)
@@ -75,7 +79,7 @@ namespace Ryujinx.Input.SDL2
{ {
if (!_isHidden) if (!_isHidden)
{ {
if (SDL_ShowCursor(SDL_DISABLE) != SDL_DISABLE) if (!SDL_HideCursor())
{ {
Logger.Error?.PrintMsg(LogClass.Application, "Failed to disable the cursor."); Logger.Error?.PrintMsg(LogClass.Application, "Failed to disable the cursor.");
} }
@@ -87,7 +91,7 @@ namespace Ryujinx.Input.SDL2
{ {
if (_isHidden) if (_isHidden)
{ {
if (SDL_ShowCursor(SDL_ENABLE) != SDL_ENABLE) if (!SDL_ShowCursor())
{ {
Logger.Error?.PrintMsg(LogClass.Application, "Failed to enable the cursor."); Logger.Error?.PrintMsg(LogClass.Application, "Failed to enable the cursor.");
} }
@@ -99,15 +103,15 @@ namespace Ryujinx.Input.SDL2
public void Update(SDL_Event evnt) public void Update(SDL_Event evnt)
{ {
switch (evnt.type) switch (evnt.Type)
{ {
case SDL_EventType.SDL_MOUSEBUTTONDOWN: case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EventType.SDL_MOUSEBUTTONUP: case SDL_EventType.SDL_EVENT_MOUSE_BUTTON_UP:
uint rawButton = evnt.button.button; uint rawButton = (uint)evnt.button.Button;
if (rawButton > 0 && rawButton <= (int)MouseButton.Count) if (rawButton is > 0 and <= ((int)MouseButton.Count))
{ {
PressedButtons[(int)DriverButtonToMouseButton(rawButton)] = evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN; PressedButtons[(int)DriverButtonToMouseButton(rawButton)] = evnt.Type == SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN;
CurrentPosition = new Vector2(evnt.button.x, evnt.button.y); CurrentPosition = new Vector2(evnt.button.x, evnt.button.y);
} }
@@ -115,13 +119,13 @@ namespace Ryujinx.Input.SDL2
break; break;
// NOTE: On Linux using Wayland mouse motion events won't be received at all. // NOTE: On Linux using Wayland mouse motion events won't be received at all.
case SDL_EventType.SDL_MOUSEMOTION: case SDL_EventType.SDL_EVENT_MOUSE_MOTION:
CurrentPosition = new Vector2(evnt.motion.x, evnt.motion.y); CurrentPosition = new Vector2(evnt.motion.x, evnt.motion.y);
_lastCursorMoveTime = Stopwatch.GetTimestamp(); _lastCursorMoveTime = Stopwatch.GetTimestamp();
break; break;
case SDL_EventType.SDL_MOUSEWHEEL: case SDL_EventType.SDL_EVENT_MOUSE_WHEEL:
Scroll = new Vector2(evnt.wheel.x, evnt.wheel.y); Scroll = new Vector2(evnt.wheel.x, evnt.wheel.y);
break; break;
@@ -143,7 +147,7 @@ namespace Ryujinx.Input.SDL2
return ClientSize; return ClientSize;
} }
public string DriverName => "SDL2"; public string DriverName => "SDL3";
public event Action<string> OnGamepadConnected public event Action<string> OnGamepadConnected
{ {
@@ -161,9 +165,11 @@ namespace Ryujinx.Input.SDL2
public IGamepad GetGamepad(string id) public IGamepad GetGamepad(string id)
{ {
return new SDL2Mouse(this); return new SDL3Mouse(this);
} }
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
public void Dispose() public void Dispose()
{ {
if (_isDisposed) if (_isDisposed)
@@ -171,6 +177,7 @@ namespace Ryujinx.Input.SDL2
return; return;
} }
GC.SuppressFinalize(this);
_isDisposed = true; _isDisposed = true;
} }
} }
@@ -1,16 +1,16 @@
using Ryujinx.SDL2.Common; using Ryujinx.SDL3.Common;
using System; using System;
namespace Ryujinx.Input.SDL2 namespace Ryujinx.Input.SDL3
{ {
public class SDL2KeyboardDriver : IGamepadDriver public class SDL3KeyboardDriver : IGamepadDriver
{ {
public SDL2KeyboardDriver() public SDL3KeyboardDriver()
{ {
SDL2Driver.Instance.Initialize(); SDL3Driver.Instance.Initialize();
} }
public string DriverName => "SDL2"; public string DriverName => "SDL3";
private static readonly string[] _keyboardIdentifers = ["0"]; private static readonly string[] _keyboardIdentifers = ["0"];
@@ -32,7 +32,7 @@ namespace Ryujinx.Input.SDL2
{ {
if (disposing) if (disposing)
{ {
SDL2Driver.Instance.Dispose(); SDL3Driver.Instance.Dispose();
} }
} }
@@ -49,7 +49,7 @@ namespace Ryujinx.Input.SDL2
return null; return null;
} }
return new SDL2Keyboard(this, _keyboardIdentifers[0], "All keyboards"); return new SDL3Keyboard(this, _keyboardIdentifers[0], "All keyboards");
} }
} }
} }
@@ -2,10 +2,11 @@
<PropertyGroup> <PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes> <DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Ryujinx.SDL2-CS-Redux" /> <PackageReference Include="ppy.SDL3-CS" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -1,23 +1,24 @@
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using SDL;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using static SDL2.SDL; using static SDL.SDL3;
namespace Ryujinx.SDL2.Common namespace Ryujinx.SDL3.Common
{ {
public class SDL2Driver : IDisposable public class SDL3Driver : IDisposable
{ {
private static SDL2Driver _instance; private static SDL3Driver _instance;
public static SDL2Driver Instance public static SDL3Driver Instance
{ {
get get
{ {
_instance ??= new SDL2Driver(); _instance ??= new SDL3Driver();
return _instance; return _instance;
} }
@@ -25,26 +26,22 @@ namespace Ryujinx.SDL2.Common
public static Action<Action> MainThreadDispatcher { get; set; } public static Action<Action> MainThreadDispatcher { get; set; }
private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO; private const SDL_InitFlags SdlInitFlags = SDL_InitFlags.SDL_INIT_EVENTS | SDL_InitFlags.SDL_INIT_GAMEPAD | SDL_InitFlags.SDL_INIT_JOYSTICK | SDL_InitFlags.SDL_INIT_AUDIO | SDL_InitFlags.SDL_INIT_VIDEO;
private bool _isRunning; private bool _isRunning;
private uint _refereceCount; private uint _refereceCount;
private Thread _worker; private Thread _worker;
private const uint SDL_JOYBATTERYUPDATED = 1543; public event Action<SDL_JoystickID> OnJoyStickConnected;
public event Action<SDL_JoystickID> OnJoystickDisconnected;
public event Action<int, int> OnJoyStickConnected;
public event Action<int> OnJoystickDisconnected;
public event Action<int, SDL_JoystickPowerLevel> OnJoyBatteryUpdated; public event Action<SDL_JoystickID, SDL_PowerState> OnJoyBatteryUpdated;
private ConcurrentDictionary<uint, Action<SDL_Event>> _registeredWindowHandlers; private ConcurrentDictionary<SDL_WindowID, Action<SDL_Event>> _registeredWindowHandlers;
private readonly Lock _lock = new(); private readonly Lock _lock = new();
private SDL2Driver() { } private SDL3Driver() { }
private const string SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS = "SDL_JOYSTICK_HIDAPI_COMBINE_JOY_CONS";
public void Initialize() public void Initialize()
{ {
@@ -58,8 +55,7 @@ namespace Ryujinx.SDL2.Common
} }
SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx"); SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS , "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "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_SWITCH_HOME_LED, "0");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
@@ -70,9 +66,9 @@ namespace Ryujinx.SDL2.Common
// We disable this behavior for now. // We disable this behavior for now.
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, "0"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS, "0");
if (SDL_Init(SdlInitFlags) != 0) 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); Logger.Error?.Print(LogClass.Application, errorMessage);
@@ -80,80 +76,77 @@ namespace Ryujinx.SDL2.Common
} }
// First ensure that we only enable joystick events (for connected/disconnected). // First ensure that we only enable joystick events (for connected/disconnected).
if (SDL_GameControllerEventState(SDL_IGNORE) != SDL_IGNORE) SDL_SetGamepadEventsEnabled(false);
SDL_SetJoystickEventsEnabled(true);
if (SDL_GamepadEventsEnabled())
{ {
Logger.Error?.PrintMsg(LogClass.Application, "Couldn't change the state of game controller events."); Logger.Error?.PrintMsg(LogClass.Application, "Couldn't change the state of game controller events.");
} }
if (SDL_JoystickEventState(SDL_ENABLE) < 0) if (!SDL_JoystickEventsEnabled())
{ {
Logger.Error?.PrintMsg(LogClass.Application, $"Failed to enable joystick event polling: {SDL_GetError()}"); Logger.Error?.PrintMsg(LogClass.Application, $"Failed to enable joystick event polling: {SDL_GetError()}");
} }
// Disable all joysticks information, we don't need them no need to flood the event queue for that. // 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_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_AXIS_MOTION, false);
SDL_EventState(SDL_EventType.SDL_JOYBALLMOTION, SDL_DISABLE); SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_BALL_MOTION, false);
SDL_EventState(SDL_EventType.SDL_JOYHATMOTION, SDL_DISABLE); SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_HAT_MOTION, false);
SDL_EventState(SDL_EventType.SDL_JOYBUTTONDOWN, SDL_DISABLE); SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_BUTTON_DOWN, false);
SDL_EventState(SDL_EventType.SDL_JOYBUTTONUP, SDL_DISABLE); SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_JOYSTICK_BUTTON_UP, false);
SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE); SDL_SetEventEnabled((uint)SDL_EventType.SDL_EVENT_GAMEPAD_SENSOR_UPDATE, false);
string gamepadDbPath = Path.Combine(AppDataManager.BaseDirPath, "SDL_GameControllerDB.txt"); string gamepadDbPath = Path.Combine(AppDataManager.BaseDirPath, "SDL_GameControllerDB.txt");
if (File.Exists(gamepadDbPath)) if (File.Exists(gamepadDbPath))
{ {
SDL_GameControllerAddMappingsFromFile(gamepadDbPath); SDL_AddGamepadMappingsFromFile(gamepadDbPath);
} }
_registeredWindowHandlers = new ConcurrentDictionary<uint, Action<SDL_Event>>(); _registeredWindowHandlers = new ConcurrentDictionary<SDL_WindowID, Action<SDL_Event>>();
_worker = new Thread(EventWorker); _worker = new Thread(EventWorker);
_isRunning = true; _isRunning = true;
_worker.Start(); _worker.Start();
} }
} }
public bool RegisterWindow(uint windowId, Action<SDL_Event> windowEventHandler) public bool RegisterWindow(SDL_WindowID windowId, Action<SDL_Event> windowEventHandler)
{ {
return _registeredWindowHandlers.TryAdd(windowId, windowEventHandler); return _registeredWindowHandlers.TryAdd(windowId, windowEventHandler);
} }
public void UnregisterWindow(uint windowId) public void UnregisterWindow(SDL_WindowID windowId)
{ {
_registeredWindowHandlers.Remove(windowId, out _); _registeredWindowHandlers.Remove(windowId, out _);
} }
private void HandleSDLEvent(ref SDL_Event evnt) private void HandleSDLEvent(ref SDL_Event evnt)
{ {
if (evnt.type == SDL_EventType.SDL_JOYDEVICEADDED) SDL_EventType type = evnt.Type;
if (type == SDL_EventType.SDL_EVENT_JOYSTICK_ADDED)
{ {
int deviceId = evnt.cbutton.which; SDL_JoystickID instanceId = evnt.jbutton.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;
}
// SDL3 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.
Logger.Debug?.Print(LogClass.Application, $"Added joystick instance id {instanceId}"); Logger.Debug?.Print(LogClass.Application, $"Added joystick instance id {instanceId}");
OnJoyStickConnected?.Invoke(deviceId, instanceId); OnJoyStickConnected?.Invoke(instanceId);
} }
else if (evnt.type == SDL_EventType.SDL_JOYDEVICEREMOVED) else if (type == SDL_EventType.SDL_EVENT_JOYSTICK_REMOVED)
{ {
Logger.Debug?.Print(LogClass.Application, $"Removed joystick instance id {evnt.cbutton.which}"); Logger.Debug?.Print(LogClass.Application, $"Removed joystick instance id {evnt.jbutton.which}");
OnJoystickDisconnected?.Invoke(evnt.cbutton.which); OnJoystickDisconnected?.Invoke(evnt.jbutton.which);
} }
else if ((uint)evnt.type == SDL_JOYBATTERYUPDATED) else if (type == SDL_EventType.SDL_EVENT_JOYSTICK_BATTERY_UPDATED)
{ {
OnJoyBatteryUpdated?.Invoke(evnt.cbutton.which, (SDL_JoystickPowerLevel)evnt.user.code); OnJoyBatteryUpdated?.Invoke(evnt.jbutton.which, evnt.jbattery.state);
} }
else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT else if (
or SDL_EventType.SDL_MOUSEBUTTONDOWN ((uint)type >= (uint)SDL_EventType.SDL_EVENT_WINDOW_FIRST && (uint)type <= (uint)SDL_EventType.SDL_EVENT_WINDOW_LAST) ||
or SDL_EventType.SDL_MOUSEBUTTONUP) type is SDL_EventType.SDL_EVENT_MOUSE_BUTTON_DOWN or SDL_EventType.SDL_EVENT_MOUSE_BUTTON_UP
)
{ {
if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action<SDL_Event> handler)) if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action<SDL_Event> handler))
{ {
@@ -162,7 +155,7 @@ namespace Ryujinx.SDL2.Common
} }
} }
private void EventWorker() private unsafe void EventWorker()
{ {
const int WaitTimeMs = 10; const int WaitTimeMs = 10;
@@ -172,7 +165,8 @@ namespace Ryujinx.SDL2.Common
{ {
MainThreadDispatcher?.Invoke(() => MainThreadDispatcher?.Invoke(() =>
{ {
while (SDL_PollEvent(out SDL_Event evnt) != 0) SDL_Event evnt = new();
while (SDL_PollEvent(&evnt))
{ {
HandleSDLEvent(ref evnt); HandleSDLEvent(ref evnt);
} }
@@ -8,6 +8,6 @@ namespace Ryujinx.UI.Common.Configuration
Dummy, Dummy,
OpenAl, OpenAl,
SoundIo, SoundIo,
SDL2, SDL3,
} }
} }
@@ -909,7 +909,7 @@ namespace Ryujinx.UI.Common.Configuration
System.EnableInternetAccess.Value = false; System.EnableInternetAccess.Value = false;
System.EnableFsIntegrityChecks.Value = true; System.EnableFsIntegrityChecks.Value = true;
System.FsGlobalAccessLogMode.Value = 0; System.FsGlobalAccessLogMode.Value = 0;
System.AudioBackend.Value = AudioBackend.SDL2; System.AudioBackend.Value = AudioBackend.SDL3;
System.AudioVolume.Value = 1; System.AudioVolume.Value = 1;
System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe; System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe;
System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB; System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB;
+3 -3
View File
@@ -6,7 +6,7 @@ using Avalonia.Threading;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
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.SDL3;
using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Audio.Backends.SoundIo;
using Ryujinx.Audio.Integration; using Ryujinx.Audio.Integration;
using Ryujinx.Ava.Common; using Ryujinx.Ava.Common;
@@ -945,7 +945,7 @@ namespace Ryujinx.Ava
{ {
var availableBackends = new List<AudioBackend> var availableBackends = new List<AudioBackend>
{ {
AudioBackend.SDL2, AudioBackend.SDL3,
AudioBackend.SoundIo, AudioBackend.SoundIo,
AudioBackend.OpenAl, AudioBackend.OpenAl,
AudioBackend.Dummy, AudioBackend.Dummy,
@@ -984,7 +984,7 @@ namespace Ryujinx.Ava
deviceDriver = currentBackend switch deviceDriver = currentBackend switch
{ {
AudioBackend.SDL2 => InitializeAudioBackend<SDL2HardwareDeviceDriver>(AudioBackend.SDL2, nextBackend), AudioBackend.SDL3 => InitializeAudioBackend<SDL3HardwareDeviceDriver>(AudioBackend.SDL3, nextBackend),
AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend), AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend),
AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend), AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend),
_ => new DummyHardwareDeviceDriver(), _ => new DummyHardwareDeviceDriver(),
+2 -2
View File
@@ -153,7 +153,7 @@
"SettingsTabSystemAudioBackendDummy": "زائف", "SettingsTabSystemAudioBackendDummy": "زائف",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "هاكات", "SettingsTabSystemHacks": "هاكات",
"SettingsTabSystemHacksNote": "قد يتسبب في عدم الاستقرار", "SettingsTabSystemHacksNote": "قد يتسبب في عدم الاستقرار",
"SettingsTabSystemDramSize": "استخدام تخطيط الذاكرة البديل (المطورين)", "SettingsTabSystemDramSize": "استخدام تخطيط الذاكرة البديل (المطورين)",
@@ -594,7 +594,7 @@
"LowPowerPptcToggleTooltip": "قم بتحميل PPTC باستخدام ثلث كمية النوى", "LowPowerPptcToggleTooltip": "قم بتحميل PPTC باستخدام ثلث كمية النوى",
"JitCacheEvictionToggleTooltip": "م بتمكين إخلاء JIT Cache لإدارة الذاكرة بكفاءة", "JitCacheEvictionToggleTooltip": "م بتمكين إخلاء JIT Cache لإدارة الذاكرة بكفاءة",
"FsIntegrityToggleTooltip": "يتحقق من وجود ملفات تالفة عند تشغيل لعبة ما، وإذا تم اكتشاف ملفات تالفة، فسيتم عرض خطأ تجزئة في السجل.\n\nليس له أي تأثير على الأداء ويهدف إلى المساعدة في استكشاف الأخطاء وإصلاحها.\n\nاتركه مفعلا إذا كنت غير متأكد.", "FsIntegrityToggleTooltip": "يتحقق من وجود ملفات تالفة عند تشغيل لعبة ما، وإذا تم اكتشاف ملفات تالفة، فسيتم عرض خطأ تجزئة في السجل.\n\nليس له أي تأثير على الأداء ويهدف إلى المساعدة في استكشاف الأخطاء وإصلاحها.\n\nاتركه مفعلا إذا كنت غير متأكد.",
"AudioBackendTooltip": "يغير الواجهة الخلفية المستخدمة لتقديم الصوت.\n\nSDL2 هو الخيار المفضل، بينما يتم استخدام OpenAL وSoundIO كبديلين. زائف لن يكون لها صوت.\n\nاضبط على SDL2 إذا لم تكن متأكدا.", "AudioBackendTooltip": "يغير الواجهة الخلفية المستخدمة لتقديم الصوت.\n\nSDL3 هو الخيار المفضل، بينما يتم استخدام OpenAL وSoundIO كبديلين. زائف لن يكون لها صوت.\n\nاضبط على SDL3 إذا لم تكن متأكدا.",
"MemoryManagerTooltip": "تغيير كيفية تعيين ذاكرة الضيف والوصول إليها. يؤثر بشكل كبير على أداء وحدة المعالجة المركزية التي تمت محاكاتها.\n\nاضبط على المضيف غير محدد إذا لم تكن متأكدا.", "MemoryManagerTooltip": "تغيير كيفية تعيين ذاكرة الضيف والوصول إليها. يؤثر بشكل كبير على أداء وحدة المعالجة المركزية التي تمت محاكاتها.\n\nاضبط على المضيف غير محدد إذا لم تكن متأكدا.",
"MemoryManagerSoftwareTooltip": "استخدام جدول الصفحات البرمجي لترجمة العناوين. أعلى دقة ولكن أبطأ أداء.", "MemoryManagerSoftwareTooltip": "استخدام جدول الصفحات البرمجي لترجمة العناوين. أعلى دقة ولكن أبطأ أداء.",
"MemoryManagerHostTooltip": "تعيين الذاكرة مباشرة في مساحة عنوان المضيف. تجميع وتنفيذ JIT أسرع بكثير.", "MemoryManagerHostTooltip": "تعيين الذاكرة مباشرة في مساحة عنوان المضيف. تجميع وتنفيذ JIT أسرع بكثير.",
+2 -2
View File
@@ -154,7 +154,7 @@
"SettingsTabSystemAudioBackendDummy": "Ohne Funktion", "SettingsTabSystemAudioBackendDummy": "Ohne Funktion",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "Hacks", "SettingsTabSystemHacks": "Hacks",
"SettingsTabSystemHacksNote": " (Kann Fehler verursachen)", "SettingsTabSystemHacksNote": " (Kann Fehler verursachen)",
"SettingsTabSystemDramSize": "DRAM Größe:", "SettingsTabSystemDramSize": "DRAM Größe:",
@@ -595,7 +595,7 @@
"LowPowerPptcToggleTooltip": "Lädt den PPTC mit einem Drittel der verfügbaren Prozessorkernen", "LowPowerPptcToggleTooltip": "Lädt den PPTC mit einem Drittel der verfügbaren Prozessorkernen",
"JitCacheEvictionToggleTooltip": "Aktivieren Sie die JIT-Cache-Eviction, um den Speicher effizient zu verwalten", "JitCacheEvictionToggleTooltip": "Aktivieren Sie die JIT-Cache-Eviction, um den Speicher effizient zu verwalten",
"FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.", "FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.",
"AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.", "AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL3 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL3 auswählen.",
"MemoryManagerTooltip": "Ändert wie der Gastspeicher abgebildet wird und wie auf ihn zugegriffen wird. Beinflusst die Leistung der emulierten CPU erheblich.\n\nIm Zweifelsfall Host ungeprüft auswählen.", "MemoryManagerTooltip": "Ändert wie der Gastspeicher abgebildet wird und wie auf ihn zugegriffen wird. Beinflusst die Leistung der emulierten CPU erheblich.\n\nIm Zweifelsfall Host ungeprüft auswählen.",
"MemoryManagerSoftwareTooltip": "Verwendung einer Software-Seitentabelle für die Adressumsetzung. Höchste Genauigkeit, aber langsamste Leistung.", "MemoryManagerSoftwareTooltip": "Verwendung einer Software-Seitentabelle für die Adressumsetzung. Höchste Genauigkeit, aber langsamste Leistung.",
"MemoryManagerHostTooltip": "Direkte Zuordnung von Speicher im Host-Adressraum. Viel schnellere JIT-Kompilierung und Ausführung.", "MemoryManagerHostTooltip": "Direkte Zuordnung von Speicher im Host-Adressraum. Viel schnellere JIT-Kompilierung und Ausführung.",
+1 -1
View File
@@ -153,7 +153,7 @@
"SettingsTabSystemAudioBackendDummy": "Απενεργοποιημένο", "SettingsTabSystemAudioBackendDummy": "Απενεργοποιημένο",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "Μικροδιορθώσεις", "SettingsTabSystemHacks": "Μικροδιορθώσεις",
"SettingsTabSystemHacksNote": " (Μπορεί να προκαλέσουν αστάθεια)", "SettingsTabSystemHacksNote": " (Μπορεί να προκαλέσουν αστάθεια)",
"SettingsTabSystemDramSize": "Μέγεθος DRAM:", "SettingsTabSystemDramSize": "Μέγεθος DRAM:",
+2 -2
View File
@@ -174,7 +174,7 @@
"SettingsTabSystemAudioBackendDummy": "Dummy", "SettingsTabSystemAudioBackendDummy": "Dummy",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemCustomVSyncInterval": "Interval", "SettingsTabSystemCustomVSyncInterval": "Interval",
"SettingsTabSystemHacks": "Hacks", "SettingsTabSystemHacks": "Hacks",
"SettingsTabSystemHacksNote": "May cause instability", "SettingsTabSystemHacksNote": "May cause instability",
@@ -626,7 +626,7 @@
"LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores", "LowPowerPptcToggleTooltip": "Load the PPTC using a third of the amount of cores",
"JitCacheEvictionToggleTooltip": "Enable JIT Cache eviction to manage memory efficiently", "JitCacheEvictionToggleTooltip": "Enable JIT Cache eviction to manage memory efficiently",
"FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.",
"AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL3 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL3 if unsure.",
"MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.",
"MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.",
"MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.",
+2 -2
View File
@@ -153,7 +153,7 @@
"SettingsTabSystemAudioBackendDummy": "Vacio", "SettingsTabSystemAudioBackendDummy": "Vacio",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "Hacks", "SettingsTabSystemHacks": "Hacks",
"SettingsTabSystemHacksNote": " (Pueden causar inestabilidad)", "SettingsTabSystemHacksNote": " (Pueden causar inestabilidad)",
"SettingsTabSystemDramSize": "Tamaño DRAM:", "SettingsTabSystemDramSize": "Tamaño DRAM:",
@@ -594,7 +594,7 @@
"LowPowerPptcToggleTooltip": "Cargue el PPTC usando un tercio de la cantidad de núcleos", "LowPowerPptcToggleTooltip": "Cargue el PPTC usando un tercio de la cantidad de núcleos",
"JitCacheEvictionToggleTooltip": "Habilite el desalojo de JIT Cache para administrar la memoria de manera eficiente", "JitCacheEvictionToggleTooltip": "Habilite el desalojo de JIT Cache para administrar la memoria de manera eficiente",
"FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.", "FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.",
"AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.", "AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL3 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL3 si no sabes qué hacer.",
"MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.\n\nSelecciona \"Host sin verificación\" si no sabes qué hacer.", "MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.\n\nSelecciona \"Host sin verificación\" si no sabes qué hacer.",
"MemoryManagerSoftwareTooltip": "Usa una tabla de paginación de software para traducir direcciones. Ofrece la precisión más exacta pero el rendimiento más lento.", "MemoryManagerSoftwareTooltip": "Usa una tabla de paginación de software para traducir direcciones. Ofrece la precisión más exacta pero el rendimiento más lento.",
"MemoryManagerHostTooltip": "Mapea la memoria directamente en la dirección de espacio del host. Compilación y ejecución JIT mucho más rápida.", "MemoryManagerHostTooltip": "Mapea la memoria directamente en la dirección de espacio del host. Compilación y ejecución JIT mucho más rápida.",
+2 -2
View File
@@ -155,7 +155,7 @@
"SettingsTabSystemAudioBackendDummy": "Factice", "SettingsTabSystemAudioBackendDummy": "Factice",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "Hacks", "SettingsTabSystemHacks": "Hacks",
"SettingsTabSystemHacksNote": "Cela peut causer des instabilités", "SettingsTabSystemHacksNote": "Cela peut causer des instabilités",
"SettingsTabSystemDramSize": "Utiliser disposition alternative de la mémoire (développeur)", "SettingsTabSystemDramSize": "Utiliser disposition alternative de la mémoire (développeur)",
@@ -597,7 +597,7 @@
"LowPowerPptcToggleTooltip": "Chargez le PPTC en utilisant un tiers de la quantité de cœurs", "LowPowerPptcToggleTooltip": "Chargez le PPTC en utilisant un tiers de la quantité de cœurs",
"JitCacheEvictionToggleTooltip": "Activer l'expulsion du cache JIT pour gérer efficacement la mémoire", "JitCacheEvictionToggleTooltip": "Activer l'expulsion du cache JIT pour gérer efficacement la mémoire",
"FsIntegrityToggleTooltip": "Vérifie si des fichiers sont corrompus lors du lancement d'un jeu, et si des fichiers corrompus sont détectés, affiche une erreur de hachage dans la console.\n\nN'a aucun impact sur les performances et est destiné à aider le dépannage.\n\nLaissez activer en cas d'incertitude.", "FsIntegrityToggleTooltip": "Vérifie si des fichiers sont corrompus lors du lancement d'un jeu, et si des fichiers corrompus sont détectés, affiche une erreur de hachage dans la console.\n\nN'a aucun impact sur les performances et est destiné à aider le dépannage.\n\nLaissez activer en cas d'incertitude.",
"AudioBackendTooltip": "Modifie le backend utilisé pour donnée un rendu audio.\n\nSDL2 est préféré, tandis que OpenAL et SoundIO sont utilisés comme backend secondaire. Le backend Dummy (Factice) ne rends aucun son.\n\nLaissez sur SDL2 si vous n'êtes pas sûr.", "AudioBackendTooltip": "Modifie le backend utilisé pour donnée un rendu audio.\n\nSDL3 est préféré, tandis que OpenAL et SoundIO sont utilisés comme backend secondaire. Le backend Dummy (Factice) ne rends aucun son.\n\nLaissez sur SDL3 si vous n'êtes pas sûr.",
"MemoryManagerTooltip": "Change la façon dont la mémoire émulée est mappée et utiliser. Cela affecte grandement les performances du processeur.\n\nRéglez sur Host Uncheked en cas d'incertitude.", "MemoryManagerTooltip": "Change la façon dont la mémoire émulée est mappée et utiliser. Cela affecte grandement les performances du processeur.\n\nRéglez sur Host Uncheked en cas d'incertitude.",
"MemoryManagerSoftwareTooltip": "Utilisez une table logicielle pour la traduction d'adresses. La plus grande précision est fournie, mais les performances en seront impacter.", "MemoryManagerSoftwareTooltip": "Utilisez une table logicielle pour la traduction d'adresses. La plus grande précision est fournie, mais les performances en seront impacter.",
"MemoryManagerHostTooltip": "Mappez directement la mémoire dans l'espace d'adresses de l'hôte. Compilation et exécution JIT beaucoup plus rapides.", "MemoryManagerHostTooltip": "Mappez directement la mémoire dans l'espace d'adresses de l'hôte. Compilation et exécution JIT beaucoup plus rapides.",
+2 -2
View File
@@ -153,7 +153,7 @@
"SettingsTabSystemAudioBackendDummy": "גולם", "SettingsTabSystemAudioBackendDummy": "גולם",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "האצות", "SettingsTabSystemHacks": "האצות",
"SettingsTabSystemHacksNote": "עלול לגרום לאי יציבות", "SettingsTabSystemHacksNote": "עלול לגרום לאי יציבות",
"SettingsTabSystemDramSize": "השתמש בפריסת זיכרון חלופית (נועד למפתחים)", "SettingsTabSystemDramSize": "השתמש בפריסת זיכרון חלופית (נועד למפתחים)",
@@ -594,7 +594,7 @@
"LowPowerPptcToggleTooltip": "טען את ה-PPTC באמצעות שליש מכמות הליבות", "LowPowerPptcToggleTooltip": "טען את ה-PPTC באמצעות שליש מכמות הליבות",
"JitCacheEvictionToggleTooltip": "אפשר פינוי JIT Cache לניהול זיכרון ביעילות", "JitCacheEvictionToggleTooltip": "אפשר פינוי JIT Cache לניהול זיכרון ביעילות",
"FsIntegrityToggleTooltip": "בודק לקבצים שגויים כאשר משחק עולה, ואם מתגלים כאלו, מציג את מזהה השגיאה שלהם לקובץ הלוג.\n\nאין לכך השפעה על הביצועים ונועד לעזור לבדיקה וניפוי שגיאות של האמולטור.\n\nמוטב להשאיר דלוק אם לא בטוחים.", "FsIntegrityToggleTooltip": "בודק לקבצים שגויים כאשר משחק עולה, ואם מתגלים כאלו, מציג את מזהה השגיאה שלהם לקובץ הלוג.\n\nאין לכך השפעה על הביצועים ונועד לעזור לבדיקה וניפוי שגיאות של האמולטור.\n\nמוטב להשאיר דלוק אם לא בטוחים.",
"AudioBackendTooltip": "משנה את אחראי השמע.\n\nSDL2 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL2 אם לא בטוחים.", "AudioBackendTooltip": "משנה את אחראי השמע.\n\nSDL3 הוא הנבחר, למראת שOpenAL וגם SoundIO משומשים כאפשרויות חלופיות. אפשרות הDummy לא תשמיע קול כלל.\n\nמוטב להשאיר על SDL3 אם לא בטוחים.",
"MemoryManagerTooltip": "שנה איך שזיכרון מארח מיוחד ומונגד. משפיע מאוד על ביצועי המעבד המדומה.\n\nמוטב להשאיר על מארח לא מבוקר אם לא בטוחים.", "MemoryManagerTooltip": "שנה איך שזיכרון מארח מיוחד ומונגד. משפיע מאוד על ביצועי המעבד המדומה.\n\nמוטב להשאיר על מארח לא מבוקר אם לא בטוחים.",
"MemoryManagerSoftwareTooltip": "השתמש בתוכנת ה-page table בכדי להתייחס לתרגומים. דיוק מרבי לקונסולה אך המימוש הכי איטי.", "MemoryManagerSoftwareTooltip": "השתמש בתוכנת ה-page table בכדי להתייחס לתרגומים. דיוק מרבי לקונסולה אך המימוש הכי איטי.",
"MemoryManagerHostTooltip": "ממפה זיכרון ישירות לכתובת המארח. מהיר בהרבה ביכולות קימפול ה-JIT והריצה.", "MemoryManagerHostTooltip": "ממפה זיכרון ישירות לכתובת המארח. מהיר בהרבה ביכולות קימפול ה-JIT והריצה.",
+2 -2
View File
@@ -153,7 +153,7 @@
"SettingsTabSystemAudioBackendDummy": "Dummy", "SettingsTabSystemAudioBackendDummy": "Dummy",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "Espedienti", "SettingsTabSystemHacks": "Espedienti",
"SettingsTabSystemHacksNote": "Possono causare instabilità", "SettingsTabSystemHacksNote": "Possono causare instabilità",
"SettingsTabSystemDramSize": "Usa layout di memoria alternativo (per sviluppatori)", "SettingsTabSystemDramSize": "Usa layout di memoria alternativo (per sviluppatori)",
@@ -595,7 +595,7 @@
"LowPowerPptcToggleTooltip": "Carica il PPTC utilizzando un terzo della quantità di core", "LowPowerPptcToggleTooltip": "Carica il PPTC utilizzando un terzo della quantità di core",
"JitCacheEvictionToggleTooltip": "Abilita l'eliminazione della cache JIT per gestire la memoria in modo efficiente", "JitCacheEvictionToggleTooltip": "Abilita l'eliminazione della cache JIT per gestire la memoria in modo efficiente",
"FsIntegrityToggleTooltip": "Controlla la presenza di file corrotti quando si avvia un gioco. Se vengono rilevati dei file corrotti, verrà mostrato un errore di hash nel log.\n\nQuesta opzione non influisce sulle prestazioni ed è pensata per facilitare la risoluzione dei problemi.\n\nNel dubbio, lascia l'opzione attiva.", "FsIntegrityToggleTooltip": "Controlla la presenza di file corrotti quando si avvia un gioco. Se vengono rilevati dei file corrotti, verrà mostrato un errore di hash nel log.\n\nQuesta opzione non influisce sulle prestazioni ed è pensata per facilitare la risoluzione dei problemi.\n\nNel dubbio, lascia l'opzione attiva.",
"AudioBackendTooltip": "Cambia il backend usato per riprodurre l'audio.\n\nSDL2 è quello preferito, mentre OpenAL e SoundIO sono usati come ripiego. Dummy non riprodurrà alcun suono.\n\nNel dubbio, imposta l'opzione su SDL2.", "AudioBackendTooltip": "Cambia il backend usato per riprodurre l'audio.\n\nSDL3 è quello preferito, mentre OpenAL e SoundIO sono usati come ripiego. Dummy non riprodurrà alcun suono.\n\nNel dubbio, imposta l'opzione su SDL3.",
"MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.\n\nNel dubbio, imposta l'opzione su Host Unchecked.", "MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.\n\nNel dubbio, imposta l'opzione su Host Unchecked.",
"MemoryManagerSoftwareTooltip": "Usa una software page table per la traduzione degli indirizzi. Massima precisione ma prestazioni più lente.", "MemoryManagerSoftwareTooltip": "Usa una software page table per la traduzione degli indirizzi. Massima precisione ma prestazioni più lente.",
"MemoryManagerHostTooltip": "Mappa direttamente la memoria nello spazio degli indirizzi dell'host. Compilazione ed esecuzione JIT molto più veloce.", "MemoryManagerHostTooltip": "Mappa direttamente la memoria nello spazio degli indirizzi dell'host. Compilazione ed esecuzione JIT molto più veloce.",
+2 -2
View File
@@ -153,7 +153,7 @@
"SettingsTabSystemAudioBackendDummy": "ダミー", "SettingsTabSystemAudioBackendDummy": "ダミー",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "ハック", "SettingsTabSystemHacks": "ハック",
"SettingsTabSystemHacksNote": " (挙動が不安定になる可能性があります)", "SettingsTabSystemHacksNote": " (挙動が不安定になる可能性があります)",
"SettingsTabSystemDramSize": "DRAMサイズ:", "SettingsTabSystemDramSize": "DRAMサイズ:",
@@ -594,7 +594,7 @@
"LowPowerPptcToggleTooltip": "コア数の 3 分の 1 を使用して PPTC をロードします。", "LowPowerPptcToggleTooltip": "コア数の 3 分の 1 を使用して PPTC をロードします。",
"JitCacheEvictionToggleTooltip": "JIT キャッシュのエビクションを有効にしてメモリを効率的に管理する", "JitCacheEvictionToggleTooltip": "JIT キャッシュのエビクションを有効にしてメモリを効率的に管理する",
"FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.", "FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.",
"AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.", "AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL3 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL3 を設定してください.",
"MemoryManagerTooltip": "ゲストメモリのマップ/アクセス方式を変更します. エミュレートされるCPUのパフォーマンスに大きな影響を与えます.\n\nよくわからない場合は「ホスト,チェックなし」を設定してください.", "MemoryManagerTooltip": "ゲストメモリのマップ/アクセス方式を変更します. エミュレートされるCPUのパフォーマンスに大きな影響を与えます.\n\nよくわからない場合は「ホスト,チェックなし」を設定してください.",
"MemoryManagerSoftwareTooltip": "アドレス変換にソフトウェアページテーブルを使用します. 非常に正確ですがパフォーマンスが大きく低下します.", "MemoryManagerSoftwareTooltip": "アドレス変換にソフトウェアページテーブルを使用します. 非常に正確ですがパフォーマンスが大きく低下します.",
"MemoryManagerHostTooltip": "ホストのアドレス空間にメモリを直接マップします.JITのコンパイルと実行速度が大きく向上します.", "MemoryManagerHostTooltip": "ホストのアドレス空間にメモリを直接マップします.JITのコンパイルと実行速度が大きく向上します.",
+2 -2
View File
@@ -156,7 +156,7 @@
"SettingsTabSystemAudioBackendDummy": "더미", "SettingsTabSystemAudioBackendDummy": "더미",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "사운드IO", "SettingsTabSystemAudioBackendSoundIO": "사운드IO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "해킹", "SettingsTabSystemHacks": "해킹",
"SettingsTabSystemHacksNote": "불안정성을 유발할 수 있음", "SettingsTabSystemHacksNote": "불안정성을 유발할 수 있음",
"SettingsTabSystemDramSize": "대체 메모리 레이아웃 사용(개발자)", "SettingsTabSystemDramSize": "대체 메모리 레이아웃 사용(개발자)",
@@ -598,7 +598,7 @@
"LowPowerPptcToggleTooltip": "코어 양의 1/3을 사용하여 PPTC를 로드합니다.", "LowPowerPptcToggleTooltip": "코어 양의 1/3을 사용하여 PPTC를 로드합니다.",
"JitCacheEvictionToggleTooltip": "메모리를 효율적으로 관리하기 위해 JIT 캐시 제거를 활성화합니다.", "JitCacheEvictionToggleTooltip": "메모리를 효율적으로 관리하기 위해 JIT 캐시 제거를 활성화합니다.",
"FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.", "FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.",
"AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL2로 설정하세요.", "AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL3가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL3로 설정하세요.",
"MemoryManagerTooltip": "게스트 메모리가 매핑되고 접속되는 방식을 변경합니다. 에뮬레이트된 CPU 성능에 크게 영향을 미칩니다.\n\n확실하지 않은 경우 호스트 확인 안함으로 설정하세요.", "MemoryManagerTooltip": "게스트 메모리가 매핑되고 접속되는 방식을 변경합니다. 에뮬레이트된 CPU 성능에 크게 영향을 미칩니다.\n\n확실하지 않은 경우 호스트 확인 안함으로 설정하세요.",
"MemoryManagerSoftwareTooltip": "주소 변환을 위해 소프트웨어 페이지 테이블을 사용하세요. 정확도는 가장 높지만 성능은 가장 느립니다.", "MemoryManagerSoftwareTooltip": "주소 변환을 위해 소프트웨어 페이지 테이블을 사용하세요. 정확도는 가장 높지만 성능은 가장 느립니다.",
"MemoryManagerHostTooltip": "호스트 주소 공간의 메모리를 직접 매핑합니다. 훨씬 빠른 JIT 컴파일 및 실행합니다.", "MemoryManagerHostTooltip": "호스트 주소 공간의 메모리를 직접 매핑합니다. 훨씬 빠른 JIT 컴파일 및 실행합니다.",
+2 -2
View File
@@ -153,7 +153,7 @@
"SettingsTabSystemAudioBackendDummy": "Atrapa", "SettingsTabSystemAudioBackendDummy": "Atrapa",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "Hacki", "SettingsTabSystemHacks": "Hacki",
"SettingsTabSystemHacksNote": " (mogą powodować niestabilność)", "SettingsTabSystemHacksNote": " (mogą powodować niestabilność)",
"SettingsTabSystemDramSize": "Użyj alternatywnego układu pamięci (Deweloperzy)", "SettingsTabSystemDramSize": "Użyj alternatywnego układu pamięci (Deweloperzy)",
@@ -594,7 +594,7 @@
"LowPowerPptcToggleTooltip": "Załaduj PPTC, używając jednej trzeciej liczby rdzeni", "LowPowerPptcToggleTooltip": "Załaduj PPTC, używając jednej trzeciej liczby rdzeni",
"JitCacheEvictionToggleTooltip": "Włącz eksmisję pamięci podręcznej JIT, aby efektywnie zarządzać pamięcią", "JitCacheEvictionToggleTooltip": "Włącz eksmisję pamięci podręcznej JIT, aby efektywnie zarządzać pamięcią",
"FsIntegrityToggleTooltip": "Sprawdza pliki podczas uruchamiania gry i jeśli zostaną wykryte uszkodzone pliki, wyświetla w dzienniku błąd hash.\n\nNie ma wpływu na wydajność i ma pomóc w rozwiązywaniu problemów.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", "FsIntegrityToggleTooltip": "Sprawdza pliki podczas uruchamiania gry i jeśli zostaną wykryte uszkodzone pliki, wyświetla w dzienniku błąd hash.\n\nNie ma wpływu na wydajność i ma pomóc w rozwiązywaniu problemów.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.",
"AudioBackendTooltip": "Zmienia backend używany do renderowania dźwięku.\n\nSDL2 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL2.", "AudioBackendTooltip": "Zmienia backend używany do renderowania dźwięku.\n\nSDL3 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL3.",
"MemoryManagerTooltip": "Zmień sposób mapowania i uzyskiwania dostępu do pamięci gości. Znacznie wpływa na wydajność emulowanego procesora.\n\nUstaw na HOST UNCHECKED, jeśli nie masz pewności.", "MemoryManagerTooltip": "Zmień sposób mapowania i uzyskiwania dostępu do pamięci gości. Znacznie wpływa na wydajność emulowanego procesora.\n\nUstaw na HOST UNCHECKED, jeśli nie masz pewności.",
"MemoryManagerSoftwareTooltip": "Użyj tabeli stron oprogramowania do translacji adresów. Najwyższa celność, ale najwolniejsza wydajność.", "MemoryManagerSoftwareTooltip": "Użyj tabeli stron oprogramowania do translacji adresów. Najwyższa celność, ale najwolniejsza wydajność.",
"MemoryManagerHostTooltip": "Bezpośrednio mapuj pamięć w przestrzeni adresowej hosta. Znacznie szybsza kompilacja i wykonanie JIT.", "MemoryManagerHostTooltip": "Bezpośrednio mapuj pamięć w przestrzeni adresowej hosta. Znacznie szybsza kompilacja i wykonanie JIT.",
+1 -1
View File
@@ -156,7 +156,7 @@
"SettingsTabSystemAudioBackendDummy": "Nenhuma", "SettingsTabSystemAudioBackendDummy": "Nenhuma",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "Hacks", "SettingsTabSystemHacks": "Hacks",
"SettingsTabSystemHacksNote": " (Pode causar instabilidade)", "SettingsTabSystemHacksNote": " (Pode causar instabilidade)",
"SettingsTabSystemDramSize": "Tamanho da DRAM:", "SettingsTabSystemDramSize": "Tamanho da DRAM:",
+2 -2
View File
@@ -174,7 +174,7 @@
"SettingsTabSystemAudioBackendDummy": "Без звука", "SettingsTabSystemAudioBackendDummy": "Без звука",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemCustomVSyncInterval": "Интервал", "SettingsTabSystemCustomVSyncInterval": "Интервал",
"SettingsTabSystemHacks": "Хаки", "SettingsTabSystemHacks": "Хаки",
"SettingsTabSystemHacksNote": "Возможна нестабильная работа", "SettingsTabSystemHacksNote": "Возможна нестабильная работа",
@@ -625,7 +625,7 @@
"LowPowerPptcToggleTooltip": "Загружает PPTC, используя треть от количества ядер.", "LowPowerPptcToggleTooltip": "Загружает PPTC, используя треть от количества ядер.",
"JitCacheEvictionToggleTooltip": "Включите вытеснение JIT-кэша для эффективного управления памятью.", "JitCacheEvictionToggleTooltip": "Включите вытеснение JIT-кэша для эффективного управления памятью.",
"FsIntegrityToggleTooltip": "Проверяет файлы при загрузке игры и если обнаружены поврежденные файлы, выводит сообщение о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для помощи в устранении неполадок.\n\nРекомендуется оставить включенным.", "FsIntegrityToggleTooltip": "Проверяет файлы при загрузке игры и если обнаружены поврежденные файлы, выводит сообщение о поврежденном хэше в журнале.\n\nНе влияет на производительность и необходим для помощи в устранении неполадок.\n\nРекомендуется оставить включенным.",
"AudioBackendTooltip": "Изменяет используемый аудио бэкенд для рендера звука.\n\nSDL2 является предпочтительным вариантом, в то время как OpenAL и SoundIO используются в качестве резервных.\n\nРекомендуется использование SDL2.", "AudioBackendTooltip": "Изменяет используемый аудио бэкенд для рендера звука.\n\nSDL3 является предпочтительным вариантом, в то время как OpenAL и SoundIO используются в качестве резервных.\n\nРекомендуется использование SDL3.",
"MemoryManagerTooltip": "Меняет разметку и доступ к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"", "MemoryManagerTooltip": "Меняет разметку и доступ к гостевой памяти. Значительно влияет на производительность процессора.\n\nРекомендуется оставить \"Хост не установлен\"",
"MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. \nСамая высокая точность, но самая низкая производительность.", "MemoryManagerSoftwareTooltip": "Использует таблицу страниц для преобразования адресов. \nСамая высокая точность, но самая низкая производительность.",
"MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. \nЗначительно более быстрые запуск и компиляция JIT.", "MemoryManagerHostTooltip": "Прямая разметка памяти в адресном пространстве хоста. \nЗначительно более быстрые запуск и компиляция JIT.",
+2 -2
View File
@@ -153,7 +153,7 @@
"SettingsTabSystemAudioBackendDummy": "Dummy", "SettingsTabSystemAudioBackendDummy": "Dummy",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "แฮ็ก", "SettingsTabSystemHacks": "แฮ็ก",
"SettingsTabSystemHacksNote": "อาจทำให้เกิดข้อผิดพลาดได้", "SettingsTabSystemHacksNote": "อาจทำให้เกิดข้อผิดพลาดได้",
"SettingsTabSystemDramSize": "ใช้รูปแบบหน่วยความจำสำรอง (โหมดนักพัฒนา)", "SettingsTabSystemDramSize": "ใช้รูปแบบหน่วยความจำสำรอง (โหมดนักพัฒนา)",
@@ -594,7 +594,7 @@
"LowPowerPptcToggleTooltip": "โหลด PPTC โดยใช้หนึ่งในสามของจำนวนคอร์", "LowPowerPptcToggleTooltip": "โหลด PPTC โดยใช้หนึ่งในสามของจำนวนคอร์",
"JitCacheEvictionToggleTooltip": "เปิดใช้งานการขับไล่ JIT Cache เพื่อจัดการหน่วยความจำอย่างมีประสิทธิภาพ", "JitCacheEvictionToggleTooltip": "เปิดใช้งานการขับไล่ JIT Cache เพื่อจัดการหน่วยความจำอย่างมีประสิทธิภาพ",
"FsIntegrityToggleTooltip": "ตรวจสอบไฟล์ที่เสียหายเมื่อบูตเกม และหากตรวจพบไฟล์ที่เสียหาย จะแสดงข้อผิดพลาดของแฮชในบันทึก\n\nไม่มีผลกระทบต่อประสิทธิภาพการทำงานและมีไว้เพื่อช่วยในการแก้ไขปัญหา\n\nปล่อยไว้หากคุณไม่แน่ใจ", "FsIntegrityToggleTooltip": "ตรวจสอบไฟล์ที่เสียหายเมื่อบูตเกม และหากตรวจพบไฟล์ที่เสียหาย จะแสดงข้อผิดพลาดของแฮชในบันทึก\n\nไม่มีผลกระทบต่อประสิทธิภาพการทำงานและมีไว้เพื่อช่วยในการแก้ไขปัญหา\n\nปล่อยไว้หากคุณไม่แน่ใจ",
"AudioBackendTooltip": "เปลี่ยนแบ็กเอนด์ที่ใช้ในการเรนเดอร์เสียง\n\nSDL2 เป็นที่ต้องการ ในขณะที่ OpenAL และ SoundIO ถูกใช้เป็นทางเลือกสำรอง ดัมมี่จะไม่มีเสียง\n\nปล่อยไว้หากคุณไม่แน่ใจ", "AudioBackendTooltip": "เปลี่ยนแบ็กเอนด์ที่ใช้ในการเรนเดอร์เสียง\n\nSDL3 เป็นที่ต้องการ ในขณะที่ OpenAL และ SoundIO ถูกใช้เป็นทางเลือกสำรอง ดัมมี่จะไม่มีเสียง\n\nปล่อยไว้หากคุณไม่แน่ใจ",
"MemoryManagerTooltip": "เปลี่ยนวิธีการแมปและเข้าถึงหน่วยความจำของผู้เยี่ยมชม ส่งผลอย่างมากต่อประสิทธิภาพการทำงานของ CPU ที่จำลอง\n\nตั้งค่าเป็น ไม่ทำการตรวจสอบ โฮสต์ หากคุณไม่แน่ใจ", "MemoryManagerTooltip": "เปลี่ยนวิธีการแมปและเข้าถึงหน่วยความจำของผู้เยี่ยมชม ส่งผลอย่างมากต่อประสิทธิภาพการทำงานของ CPU ที่จำลอง\n\nตั้งค่าเป็น ไม่ทำการตรวจสอบ โฮสต์ หากคุณไม่แน่ใจ",
"MemoryManagerSoftwareTooltip": "ใช้ตารางหน้าซอฟต์แวร์สำหรับการแปลที่อยู่ ความแม่นยำสูงสุดแต่ประสิทธิภาพช้าที่สุด", "MemoryManagerSoftwareTooltip": "ใช้ตารางหน้าซอฟต์แวร์สำหรับการแปลที่อยู่ ความแม่นยำสูงสุดแต่ประสิทธิภาพช้าที่สุด",
"MemoryManagerHostTooltip": "แมปหน่วยความจำในพื้นที่ที่อยู่โฮสต์โดยตรง การคอมไพล์และดำเนินการ JIT เร็วขึ้นมาก", "MemoryManagerHostTooltip": "แมปหน่วยความจำในพื้นที่ที่อยู่โฮสต์โดยตรง การคอมไพล์และดำเนินการ JIT เร็วขึ้นมาก",
+2 -2
View File
@@ -153,7 +153,7 @@
"SettingsTabSystemAudioBackendDummy": "Yapay", "SettingsTabSystemAudioBackendDummy": "Yapay",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "Hack'ler", "SettingsTabSystemHacks": "Hack'ler",
"SettingsTabSystemHacksNote": " (dengesizlik oluşturabilir)", "SettingsTabSystemHacksNote": " (dengesizlik oluşturabilir)",
"SettingsTabSystemDramSize": "Alternatif bellek düzeni kullan (Geliştirici)", "SettingsTabSystemDramSize": "Alternatif bellek düzeni kullan (Geliştirici)",
@@ -594,7 +594,7 @@
"LowPowerPptcToggleTooltip": "Çekirdek miktarının üçte birini kullanarak PPTC'yi yükleyin", "LowPowerPptcToggleTooltip": "Çekirdek miktarının üçte birini kullanarak PPTC'yi yükleyin",
"JitCacheEvictionToggleTooltip": "Belleği verimli bir şekilde yönetmek için JIT Önbellek tahliyesini etkinleştirin", "JitCacheEvictionToggleTooltip": "Belleği verimli bir şekilde yönetmek için JIT Önbellek tahliyesini etkinleştirin",
"FsIntegrityToggleTooltip": "Oyun açarken hatalı dosyaların olup olmadığını kontrol eder, ve hatalı dosya bulursa log dosyasında hash hatası görüntüler.\n\nPerformansa herhangi bir etkisi yoktur ve sorun gidermeye yardımcı olur.\n\nEmin değilseniz aktif halde bırakın.", "FsIntegrityToggleTooltip": "Oyun açarken hatalı dosyaların olup olmadığını kontrol eder, ve hatalı dosya bulursa log dosyasında hash hatası görüntüler.\n\nPerformansa herhangi bir etkisi yoktur ve sorun gidermeye yardımcı olur.\n\nEmin değilseniz aktif halde bırakın.",
"AudioBackendTooltip": "Ses çıkış motorunu değiştirir.\n\nSDL2 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL2 seçeneğine ayarlayın.", "AudioBackendTooltip": "Ses çıkış motorunu değiştirir.\n\nSDL3 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL3 seçeneğine ayarlayın.",
"MemoryManagerTooltip": "Guest hafızasının nasıl tahsis edilip erişildiğini değiştirir. Emüle edilen CPU performansını ciddi biçimde etkiler.\n\nEmin değilseniz HOST UNCHECKED seçeneğine ayarlayın.", "MemoryManagerTooltip": "Guest hafızasının nasıl tahsis edilip erişildiğini değiştirir. Emüle edilen CPU performansını ciddi biçimde etkiler.\n\nEmin değilseniz HOST UNCHECKED seçeneğine ayarlayın.",
"MemoryManagerSoftwareTooltip": "Adres çevirisi için bir işlemci sayfası kullanır. En yüksek doğruluğu ve en yavaş performansı sunar.", "MemoryManagerSoftwareTooltip": "Adres çevirisi için bir işlemci sayfası kullanır. En yüksek doğruluğu ve en yavaş performansı sunar.",
"MemoryManagerHostTooltip": "Hafızayı doğrudan host adres aralığında tahsis eder. Çok daha hızlı JIT derleme ve işletimi sunar.", "MemoryManagerHostTooltip": "Hafızayı doğrudan host adres aralığında tahsis eder. Çok daha hızlı JIT derleme ve işletimi sunar.",
+2 -2
View File
@@ -156,7 +156,7 @@
"SettingsTabSystemAudioBackendDummy": "Dummy", "SettingsTabSystemAudioBackendDummy": "Dummy",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "Хитрощі", "SettingsTabSystemHacks": "Хитрощі",
"SettingsTabSystemHacksNote": " (може викликати нестабільність)", "SettingsTabSystemHacksNote": " (може викликати нестабільність)",
"SettingsTabSystemDramSize": "Використовувати альтернативне розташування пам'яті (розробники)", "SettingsTabSystemDramSize": "Використовувати альтернативне розташування пам'яті (розробники)",
@@ -598,7 +598,7 @@
"LowPowerPptcToggleTooltip": "Завантажте PPTC, використовуючи третину кількості ядер", "LowPowerPptcToggleTooltip": "Завантажте PPTC, використовуючи третину кількості ядер",
"JitCacheEvictionToggleTooltip": "Увімкніть видалення кешу JIT для ефективного керування пам’яттю", "JitCacheEvictionToggleTooltip": "Увімкніть видалення кешу JIT для ефективного керування пам’яттю",
"FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.", "FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.",
"AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.", "AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL3 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL3, якщо не впевнені.",
"MemoryManagerTooltip": "Змінює спосіб відображення та доступу до гостьової пам’яті. Значно впливає на продуктивність емульованого ЦП.\n\nВстановіть «Неперевірений хост», якщо не впевнені.", "MemoryManagerTooltip": "Змінює спосіб відображення та доступу до гостьової пам’яті. Значно впливає на продуктивність емульованого ЦП.\n\nВстановіть «Неперевірений хост», якщо не впевнені.",
"MemoryManagerSoftwareTooltip": "Використовує програмну таблицю сторінок для перекладу адрес. Найвища точність, але найповільніша продуктивність.", "MemoryManagerSoftwareTooltip": "Використовує програмну таблицю сторінок для перекладу адрес. Найвища точність, але найповільніша продуктивність.",
"MemoryManagerHostTooltip": "Пряме відображення пам'яті в адресному просторі хосту. Набагато швидша компіляція та виконання JIT.", "MemoryManagerHostTooltip": "Пряме відображення пам'яті в адресному просторі хосту. Набагато швидша компіляція та виконання JIT.",
+2 -2
View File
@@ -156,7 +156,7 @@
"SettingsTabSystemAudioBackendDummy": "无", "SettingsTabSystemAudioBackendDummy": "无",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "修改", "SettingsTabSystemHacks": "修改",
"SettingsTabSystemHacksNote": "会导致模拟器不稳定", "SettingsTabSystemHacksNote": "会导致模拟器不稳定",
"SettingsTabSystemDramSize": "使用开发机的内存布局(开发人员使用)", "SettingsTabSystemDramSize": "使用开发机的内存布局(开发人员使用)",
@@ -598,7 +598,7 @@
"LowPowerPptcToggleTooltip": "使用三分之一的核心数量加载 PPTC", "LowPowerPptcToggleTooltip": "使用三分之一的核心数量加载 PPTC",
"JitCacheEvictionToggleTooltip": "启用 JIT 缓存驱逐以有效管理内存", "JitCacheEvictionToggleTooltip": "启用 JIT 缓存驱逐以有效管理内存",
"FsIntegrityToggleTooltip": "启动游戏时检查游戏文件的完整性,并在日志中记录损坏的文件。\n\n对性能没有影响,用于排查故障。\n\n如果不确定,请保持开启状态。", "FsIntegrityToggleTooltip": "启动游戏时检查游戏文件的完整性,并在日志中记录损坏的文件。\n\n对性能没有影响,用于排查故障。\n\n如果不确定,请保持开启状态。",
"AudioBackendTooltip": "更改音频处理引擎。\n\n推荐选择“SDL2”,另外“OpenAL”和“SoundIO”可以作为备选,选择“无”将没有声音。\n\n如果不确定,请设置为“SDL2”。", "AudioBackendTooltip": "更改音频处理引擎。\n\n推荐选择“SDL3”,另外“OpenAL”和“SoundIO”可以作为备选,选择“无”将没有声音。\n\n如果不确定,请设置为“SDL3”。",
"MemoryManagerTooltip": "更改模拟器内存映射和访问的方式,对模拟器 CPU 的性能影响很大。\n\n如果不确定,请设置为“跳过检查的本机映射”。", "MemoryManagerTooltip": "更改模拟器内存映射和访问的方式,对模拟器 CPU 的性能影响很大。\n\n如果不确定,请设置为“跳过检查的本机映射”。",
"MemoryManagerSoftwareTooltip": "使用软件内存页进行内存地址映射,最准确但是速度最慢。", "MemoryManagerSoftwareTooltip": "使用软件内存页进行内存地址映射,最准确但是速度最慢。",
"MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译和执行的效率更高。", "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,使得即时编译和执行的效率更高。",
+2 -2
View File
@@ -156,7 +156,7 @@
"SettingsTabSystemAudioBackendDummy": "虛設 (Dummy)", "SettingsTabSystemAudioBackendDummy": "虛設 (Dummy)",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL", "SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO", "SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2", "SettingsTabSystemAudioBackendSDL3": "SDL3",
"SettingsTabSystemHacks": "補釘修正", "SettingsTabSystemHacks": "補釘修正",
"SettingsTabSystemHacksNote": "可能導致模擬器不穩定", "SettingsTabSystemHacksNote": "可能導致模擬器不穩定",
"SettingsTabSystemDramSize": "使用替代的記憶體配置 (開發者專用)", "SettingsTabSystemDramSize": "使用替代的記憶體配置 (開發者專用)",
@@ -598,7 +598,7 @@
"LowPowerPptcToggleTooltip": "使用三分之一的核心數量加載 PPTC", "LowPowerPptcToggleTooltip": "使用三分之一的核心數量加載 PPTC",
"JitCacheEvictionToggleTooltip": "啟用 JIT 快取驅逐以有效管理內存", "JitCacheEvictionToggleTooltip": "啟用 JIT 快取驅逐以有效管理內存",
"FsIntegrityToggleTooltip": "在啟動遊戲時檢查損壞的檔案,如果檢測到損壞的檔案,則在日誌中顯示雜湊值錯誤。\n\n對效能沒有影響,旨在幫助排除故障。\n\n如果不確定,請保持開啟狀態。", "FsIntegrityToggleTooltip": "在啟動遊戲時檢查損壞的檔案,如果檢測到損壞的檔案,則在日誌中顯示雜湊值錯誤。\n\n對效能沒有影響,旨在幫助排除故障。\n\n如果不確定,請保持開啟狀態。",
"AudioBackendTooltip": "變更用於繪製音訊的後端。\n\nSDL2 是首選,而 OpenAL 和 SoundIO 則作為備用。虛設 (Dummy) 將沒有聲音。\n\n如果不確定,請設定為 SDL2。", "AudioBackendTooltip": "變更用於繪製音訊的後端。\n\nSDL3 是首選,而 OpenAL 和 SoundIO 則作為備用。虛設 (Dummy) 將沒有聲音。\n\n如果不確定,請設定為 SDL3。",
"MemoryManagerTooltip": "變更客體記憶體的映射和存取方式。這會極大地影響模擬 CPU 效能。\n\n如果不確定,請設定為主體略過檢查模式。", "MemoryManagerTooltip": "變更客體記憶體的映射和存取方式。這會極大地影響模擬 CPU 效能。\n\n如果不確定,請設定為主體略過檢查模式。",
"MemoryManagerSoftwareTooltip": "使用軟體分頁表進行位址轉換。精度最高,但效能最差。", "MemoryManagerSoftwareTooltip": "使用軟體分頁表進行位址轉換。精度最高,但效能最差。",
"MemoryManagerHostTooltip": "直接映射主體位址空間中的記憶體。更快的 JIT 編譯和執行速度。", "MemoryManagerHostTooltip": "直接映射主體位址空間中的記憶體。更快的 JIT 編譯和執行速度。",
+4 -4
View File
@@ -1,6 +1,6 @@
using DiscordRPC; using DiscordRPC;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.SDL2; using Ryujinx.Audio.Backends.SDL3;
using Ryujinx.Ava; using Ryujinx.Ava;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
@@ -158,7 +158,7 @@ namespace Ryujinx.Headless
config = new StandardControllerInputConfig config = new StandardControllerInputConfig
{ {
Version = InputConfig.CurrentVersion, Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2, Backend = InputBackendType.GamepadSDL3,
Id = null, Id = null,
ControllerType = ControllerType.JoyconPair, ControllerType = ControllerType.JoyconPair,
DeadzoneLeft = 0.1f, DeadzoneLeft = 0.1f,
@@ -307,7 +307,7 @@ namespace Ryujinx.Headless
return new VulkanRenderer( return new VulkanRenderer(
api, api,
(instance, _) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))), (instance, _) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))),
vulkanWindow.GetRequiredInstanceExtensions, VulkanWindow.GetRequiredInstanceExtensions,
preferredGpuId); preferredGpuId);
} }
@@ -331,7 +331,7 @@ namespace Ryujinx.Headless
_accountManager, _accountManager,
_userChannelPersistence, _userChannelPersistence,
renderer, renderer,
new SDL2HardwareDeviceDriver(), new SDL3HardwareDeviceDriver(),
options.DramSize, options.DramSize,
window, window,
options.SystemLanguage, options.SystemLanguage,
+6 -5
View File
@@ -19,9 +19,10 @@ using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2; using Ryujinx.Input.SDL3;
using Ryujinx.SDL2.Common; using Ryujinx.SDL3.Common;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using SDL;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -58,7 +59,7 @@ namespace Ryujinx.Headless
AutoResetEvent invoked = new(false); AutoResetEvent invoked = new(false);
// MacOS must perform SDL polls from the main thread. // MacOS must perform SDL polls from the main thread.
SDL2Driver.MainThreadDispatcher = action => SDL3Driver.MainThreadDispatcher = action =>
{ {
invoked.Reset(); invoked.Reset();
@@ -169,7 +170,7 @@ namespace Ryujinx.Headless
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile); _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
_userChannelPersistence = new UserChannelPersistence(); _userChannelPersistence = new UserChannelPersistence();
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver()); _inputManager = new InputManager(new SDL3KeyboardDriver(), new SDL3GamepadDriver());
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache; GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
@@ -367,7 +368,7 @@ namespace Ryujinx.Headless
_window = window; _window = window;
_window.IsFullscreen = options.IsFullscreen; _window.IsFullscreen = options.IsFullscreen;
_window.DisplayId = options.DisplayId; _window.DisplayId = (SDL_DisplayID)options.DisplayId;
_window.IsExclusiveFullscreen = options.IsExclusiveFullscreen; _window.IsExclusiveFullscreen = options.IsExclusiveFullscreen;
_window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth; _window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth;
_window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight; _window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight;
+42 -40
View File
@@ -4,16 +4,17 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Graphics.OpenGL; using Ryujinx.Graphics.OpenGL;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using SDL;
using System; using System;
using static SDL2.SDL; using static SDL.SDL3;
namespace Ryujinx.Headless namespace Ryujinx.Headless
{ {
class OpenGLWindow : WindowBase unsafe class OpenGLWindow : WindowBase
{ {
private static void CheckResult(int result) private static void CheckResult(bool result)
{ {
if (result < 0) if (!result)
{ {
throw new InvalidOperationException($"SDL_GL function returned an error: {SDL_GetError()}"); throw new InvalidOperationException($"SDL_GL function returned an error: {SDL_GetError()}");
} }
@@ -21,21 +22,21 @@ namespace Ryujinx.Headless
private static void SetupOpenGLAttributes(bool sharedContext, GraphicsDebugLevel debugLevel) private static void SetupOpenGLAttributes(bool sharedContext, GraphicsDebugLevel debugLevel)
{ {
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MAJOR_VERSION, 4));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 3)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_MINOR_VERSION, 3));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_PROFILE_MASK, (int)SDL_GLProfile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_CONTEXT_FLAGS, debugLevel != GraphicsDebugLevel.None ? (int)SDL_GLcontext.SDL_GL_CONTEXT_DEBUG_FLAG : 0)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_CONTEXT_FLAGS, debugLevel != GraphicsDebugLevel.None ? (int)SDL_GLContextFlag.SDL_GL_CONTEXT_DEBUG_FLAG : 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, sharedContext ? 1 : 0)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, sharedContext ? 1 : 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ACCELERATED_VISUAL, 1)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_ACCELERATED_VISUAL, 1));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_RED_SIZE, 8)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_RED_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_GREEN_SIZE, 8)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_GREEN_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_BLUE_SIZE, 8)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_BLUE_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_ALPHA_SIZE, 8)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_ALPHA_SIZE, 8));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DEPTH_SIZE, 16)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_DEPTH_SIZE, 16));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STENCIL_SIZE, 0)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_STENCIL_SIZE, 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_DOUBLEBUFFER, 1));
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_STEREO, 0)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_STEREO, 0));
} }
private class OpenToolkitBindingsContext : IBindingsContext private class OpenToolkitBindingsContext : IBindingsContext
@@ -46,35 +47,35 @@ namespace Ryujinx.Headless
} }
} }
private class SDL2OpenGLContext : IOpenGLContext private class SDL3OpenGLContext : IOpenGLContext
{ {
private readonly nint _context; private readonly SDL_GLContextState* _context;
private readonly nint _window; private readonly SDL_Window* _window;
private readonly bool _shouldDisposeWindow; private readonly bool _shouldDisposeWindow;
public SDL2OpenGLContext(nint context, nint window, bool shouldDisposeWindow = true) public SDL3OpenGLContext(SDL_GLContextState* context, SDL_Window* window, bool shouldDisposeWindow = true)
{ {
_context = context; _context = context;
_window = window; _window = window;
_shouldDisposeWindow = shouldDisposeWindow; _shouldDisposeWindow = shouldDisposeWindow;
} }
public static SDL2OpenGLContext CreateBackgroundContext(SDL2OpenGLContext sharedContext) public static SDL3OpenGLContext CreateBackgroundContext(SDL3OpenGLContext sharedContext)
{ {
sharedContext.MakeCurrent(); sharedContext.MakeCurrent();
// Ensure we share our contexts. // Ensure we share our contexts.
SetupOpenGLAttributes(true, GraphicsDebugLevel.None); SetupOpenGLAttributes(true, GraphicsDebugLevel.None);
nint windowHandle = SDL_CreateWindow("Ryujinx background context window", 0, 0, 1, 1, SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN); SDL_Window* windowHandle = SDL_CreateWindow("Ryujinx background context window", 1, 1, SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN);
nint context = SDL_GL_CreateContext(windowHandle); SDL_GLContextState* context = SDL_GL_CreateContext(windowHandle);
GL.LoadBindings(new OpenToolkitBindingsContext()); GL.LoadBindings(new OpenToolkitBindingsContext());
CheckResult(SDL_GL_SetAttribute(SDL_GLattr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0)); CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0));
CheckResult(SDL_GL_MakeCurrent(windowHandle, nint.Zero)); CheckResult(SDL_GL_MakeCurrent(windowHandle, null));
return new SDL2OpenGLContext(context, windowHandle); return new SDL3OpenGLContext(context, windowHandle);
} }
public void MakeCurrent() public void MakeCurrent()
@@ -84,9 +85,9 @@ namespace Ryujinx.Headless
return; return;
} }
int res = SDL_GL_MakeCurrent(_window, _context); bool res = SDL_GL_MakeCurrent(_window, _context);
if (res != 0) if (!res)
{ {
string errorMessage = $"SDL_GL_CreateContext failed with error \"{SDL_GetError()}\""; string errorMessage = $"SDL_GL_CreateContext failed with error \"{SDL_GetError()}\"";
@@ -96,11 +97,11 @@ namespace Ryujinx.Headless
} }
} }
public bool HasContext() => SDL_GL_GetCurrentContext() != nint.Zero; public bool HasContext() => SDL_GL_GetCurrentContext() != null;
public void Dispose() public void Dispose()
{ {
SDL_GL_DeleteContext(_context); SDL_GL_DestroyContext (_context);
if (_shouldDisposeWindow) if (_shouldDisposeWindow)
{ {
@@ -110,7 +111,7 @@ namespace Ryujinx.Headless
} }
private readonly GraphicsDebugLevel _glLogLevel; private readonly GraphicsDebugLevel _glLogLevel;
private SDL2OpenGLContext _openGLContext; private SDL3OpenGLContext _openGLContext;
public OpenGLWindow( public OpenGLWindow(
InputManager inputManager, InputManager inputManager,
@@ -124,16 +125,16 @@ namespace Ryujinx.Headless
_glLogLevel = glLogLevel; _glLogLevel = glLogLevel;
} }
public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_OPENGL; public override SDL_WindowFlags WindowFlags => SDL_WindowFlags.SDL_WINDOW_OPENGL;
protected override void InitializeWindowRenderer() protected override void InitializeWindowRenderer()
{ {
// Ensure to not share this context with other contexts before this point. // Ensure to not share this context with other contexts before this point.
SetupOpenGLAttributes(false, _glLogLevel); SetupOpenGLAttributes(false, _glLogLevel);
nint context = SDL_GL_CreateContext(WindowHandle); SDL_GLContextState* context = SDL_GL_CreateContext(WindowHandle);
CheckResult(SDL_GL_SetSwapInterval(1)); CheckResult(SDL_GL_SetSwapInterval(1));
if (context == nint.Zero) if (context == null)
{ {
string errorMessage = $"SDL_GL_CreateContext failed with error \"{SDL_GetError()}\""; string errorMessage = $"SDL_GL_CreateContext failed with error \"{SDL_GetError()}\"";
@@ -143,10 +144,10 @@ namespace Ryujinx.Headless
} }
// NOTE: The window handle needs to be disposed by the thread that created it and is handled separately. // NOTE: The window handle needs to be disposed by the thread that created it and is handled separately.
_openGLContext = new SDL2OpenGLContext(context, WindowHandle, false); _openGLContext = new SDL3OpenGLContext(context, WindowHandle, false);
// First take exclusivity on the OpenGL context. // First take exclusivity on the OpenGL context.
((OpenGLRenderer)Renderer).InitializeBackgroundContext(SDL2OpenGLContext.CreateBackgroundContext(_openGLContext)); ((OpenGLRenderer)Renderer).InitializeBackgroundContext(SDL3OpenGLContext.CreateBackgroundContext(_openGLContext));
_openGLContext.MakeCurrent(); _openGLContext.MakeCurrent();
@@ -162,7 +163,8 @@ namespace Ryujinx.Headless
else if (IsFullscreen) else if (IsFullscreen)
{ {
// NOTE: grabbing the main display's dimensions directly as OpenGL doesn't scale along like the VulkanWindow. // NOTE: grabbing the main display's dimensions directly as OpenGL doesn't scale along like the VulkanWindow.
if (SDL_GetDisplayBounds(DisplayId, out SDL_Rect displayBounds) < 0) SDL_Rect displayBounds = new();
if (!SDL_GetDisplayBounds(DisplayId, &displayBounds))
{ {
Logger.Warning?.Print(LogClass.Application, $"Could not retrieve display bounds: {SDL_GetError()}"); Logger.Warning?.Print(LogClass.Application, $"Could not retrieve display bounds: {SDL_GetError()}");
@@ -191,7 +193,7 @@ namespace Ryujinx.Headless
Device.DisposeGpu(); Device.DisposeGpu();
// Unbind context and destroy everything // Unbind context and destroy everything
CheckResult(SDL_GL_MakeCurrent(WindowHandle, nint.Zero)); CheckResult(SDL_GL_MakeCurrent(WindowHandle, null));
_openGLContext.Dispose(); _openGLContext.Dispose();
} }
+23 -38
View File
@@ -1,17 +1,16 @@
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using Ryujinx.SDL2.Common; using Ryujinx.SDL3.Common;
using SDL;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using static SDL2.SDL; using static SDL.SDL3;
namespace Ryujinx.Headless namespace Ryujinx.Headless
{ {
class VulkanWindow : WindowBase class VulkanWindow : WindowBase
{ {
private readonly GraphicsDebugLevel _glLogLevel;
public VulkanWindow( public VulkanWindow(
InputManager inputManager, InputManager inputManager,
GraphicsDebugLevel glLogLevel, GraphicsDebugLevel glLogLevel,
@@ -21,10 +20,9 @@ namespace Ryujinx.Headless
bool ignoreControllerApplet) bool ignoreControllerApplet)
: base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet) : base(inputManager, glLogLevel, aspectRatio, enableMouse, hideCursorMode, ignoreControllerApplet)
{ {
_glLogLevel = glLogLevel;
} }
public override SDL_WindowFlags GetWindowFlags() => SDL_WindowFlags.SDL_WINDOW_VULKAN; public override SDL_WindowFlags WindowFlags => SDL_WindowFlags.SDL_WINDOW_VULKAN;
protected override void InitializeWindowRenderer() { } protected override void InitializeWindowRenderer() { }
@@ -42,18 +40,15 @@ namespace Ryujinx.Headless
} }
} }
private static void BasicInvoke(Action action) public unsafe nint CreateWindowSurface(nint instance)
{ {
action(); VkSurfaceKHR_T surface = new();
} VkSurfaceKHR_T* surfaceHandle = &surface;
VkSurfaceKHR_T** surfaceHandleHandle = &surfaceHandle;
public nint CreateWindowSurface(nint instance)
{
ulong surfaceHandle = 0;
void CreateSurface() void CreateSurface()
{ {
if (SDL_Vulkan_CreateSurface(WindowHandle, instance, out surfaceHandle) == SDL_bool.SDL_FALSE) if (!SDL_Vulkan_CreateSurface(WindowHandle, (VkInstance_T*)instance, null, surfaceHandleHandle))
{ {
string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\""; string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\"";
@@ -63,9 +58,9 @@ namespace Ryujinx.Headless
} }
} }
if (SDL2Driver.MainThreadDispatcher != null) if (SDL3Driver.MainThreadDispatcher != null)
{ {
SDL2Driver.MainThreadDispatcher(CreateSurface); SDL3Driver.MainThreadDispatcher(CreateSurface);
} }
else else
{ {
@@ -75,32 +70,22 @@ namespace Ryujinx.Headless
return (nint)surfaceHandle; return (nint)surfaceHandle;
} }
public unsafe string[] GetRequiredInstanceExtensions() public unsafe static string[] GetRequiredInstanceExtensions()
{ {
if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out uint extensionsCount, nint.Zero) == SDL_bool.SDL_TRUE) uint extensionCount = 0;
{ byte** extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount);
nint[] rawExtensions = new nint[(int)extensionsCount]; if (extensionCount == 0) {
string[] extensions = new string[(int)extensionsCount]; string errorMessage = $"SDL_Vulkan_GetInstanceExtensions failed with error \"{SDL_GetError()}\"";
fixed (nint* rawExtensionsPtr = rawExtensions) Logger.Error?.Print(LogClass.Application, errorMessage);
{
if (SDL_Vulkan_GetInstanceExtensions(WindowHandle, out extensionsCount, (nint)rawExtensionsPtr) == SDL_bool.SDL_TRUE)
{
for (int i = 0; i < extensions.Length; i++)
{
extensions[i] = Marshal.PtrToStringUTF8(rawExtensions[i]);
}
return extensions; throw new Exception(errorMessage);
}
}
} }
string[] extensionArr = new string[extensionCount];
string errorMessage = $"SDL_Vulkan_GetInstanceExtensions failed with error \"{SDL_GetError()}\""; for (int i = 0; i < extensionCount; i++) {
extensionArr[i] = Marshal.PtrToStringUTF8((nint)extensions[i]);
Logger.Error?.Print(LogClass.Application, errorMessage); }
return extensionArr;
throw new Exception(errorMessage);
} }
protected override void FinalizeWindowRenderer() protected override void FinalizeWindowRenderer()
+66 -54
View File
@@ -1,3 +1,4 @@
using LibHac.Util;
using Ryujinx.Ava; using Ryujinx.Ava;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
@@ -12,36 +13,32 @@ using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationPr
using Ryujinx.HLE.UI; using Ryujinx.HLE.UI;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2; using Ryujinx.Input.SDL3;
using Ryujinx.SDL2.Common; using Ryujinx.SDL3.Common;
using SDL;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using static SDL2.SDL; using static SDL.SDL3;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing; using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter; using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
using Switch = Ryujinx.HLE.Switch; using Switch = Ryujinx.HLE.Switch;
namespace Ryujinx.Headless namespace Ryujinx.Headless
{ {
abstract partial class WindowBase : IHostUIHandler, IDisposable abstract unsafe class WindowBase : IHostUIHandler, IDisposable
{ {
protected const int DefaultWidth = 1280; protected const int DefaultWidth = 1280;
protected const int DefaultHeight = 720; protected const int DefaultHeight = 720;
private const int TargetFps = 60; private const int TargetFps = 60;
private SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL_WindowFlags.SDL_WINDOW_SHOWN; private SDL_WindowFlags DefaultFlags = SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS;
private SDL_WindowFlags FullscreenFlag = 0; private SDL_WindowFlags FullscreenFlag = 0;
private static readonly ConcurrentQueue<Action> _mainThreadActions = new(); private static readonly ConcurrentQueue<Action> _mainThreadActions = new();
[LibraryImport("SDL2")]
// TODO: Remove this as soon as SDL2-CS was updated to expose this method publicly
private static partial nint SDL_LoadBMP_RW(nint src, int freesrc);
public static void QueueMainThreadAction(Action action) public static void QueueMainThreadAction(Action action)
{ {
_mainThreadActions.Enqueue(action); _mainThreadActions.Enqueue(action);
@@ -54,12 +51,12 @@ namespace Ryujinx.Headless
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent; public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
protected nint WindowHandle { get; set; } protected SDL_Window* WindowHandle { get; set; }
public IHostUITheme HostUITheme { get; } public IHostUITheme HostUITheme { get; }
public int Width { get; private set; } public int Width { get; private set; }
public int Height { get; private set; } public int Height { get; private set; }
public int DisplayId { get; set; } public SDL_DisplayID DisplayId { get; set; }
public bool IsFullscreen { get; set; } public bool IsFullscreen { get; set; }
public bool IsExclusiveFullscreen { get; set; } public bool IsExclusiveFullscreen { get; set; }
public int ExclusiveFullscreenWidth { get; set; } public int ExclusiveFullscreenWidth { get; set; }
@@ -68,7 +65,7 @@ namespace Ryujinx.Headless
public ScalingFilter ScalingFilter { get; set; } public ScalingFilter ScalingFilter { get; set; }
public int ScalingFilterLevel { get; set; } public int ScalingFilterLevel { get; set; }
protected SDL2MouseDriver MouseDriver; protected SDL3MouseDriver MouseDriver;
private readonly InputManager _inputManager; private readonly InputManager _inputManager;
private readonly IKeyboard _keyboardInterface; private readonly IKeyboard _keyboardInterface;
private readonly GraphicsDebugLevel _glLogLevel; private readonly GraphicsDebugLevel _glLogLevel;
@@ -81,7 +78,7 @@ namespace Ryujinx.Headless
private long _ticks; private long _ticks;
private bool _isActive; private bool _isActive;
private bool _isStopped; private bool _isStopped;
private uint _windowId; private SDL_WindowID _windowId;
private string _gpuDriverName; private string _gpuDriverName;
@@ -97,7 +94,7 @@ namespace Ryujinx.Headless
HideCursorMode hideCursorMode, HideCursorMode hideCursorMode,
bool ignoreControllerApplet) bool ignoreControllerApplet)
{ {
MouseDriver = new SDL2MouseDriver(hideCursorMode); MouseDriver = new SDL3MouseDriver(hideCursorMode);
_inputManager = inputManager; _inputManager = inputManager;
_inputManager.SetMouseDriver(MouseDriver); _inputManager.SetMouseDriver(MouseDriver);
NpadManager = _inputManager.CreateNpadManager(); NpadManager = _inputManager.CreateNpadManager();
@@ -114,7 +111,7 @@ namespace Ryujinx.Headless
_ignoreControllerApplet = ignoreControllerApplet; _ignoreControllerApplet = ignoreControllerApplet;
HostUITheme = new HeadlessHostUiTheme(); HostUITheme = new HeadlessHostUiTheme();
SDL2Driver.Instance.Initialize(); SDL3Driver.Instance.Initialize();
} }
public void Initialize(Switch device, List<InputConfig> inputConfigs, bool enableKeyboard, bool enableMouse) public void Initialize(Switch device, List<InputConfig> inputConfigs, bool enableKeyboard, bool enableMouse)
@@ -149,16 +146,13 @@ namespace Ryujinx.Headless
iconStream.Close(); iconStream.Close();
unsafe fixed (byte* iconPtr = iconBytes)
{ {
fixed (byte* iconPtr = iconBytes) SDL_IOStream* rwOpsStruct = SDL_IOFromConstMem((nint)iconPtr, (UIntPtr)iconBytes.Length);
{ SDL_Surface* iconHandle = SDL_LoadBMP_IO(rwOpsStruct, true);
nint rwOpsStruct = SDL_RWFromConstMem((nint)iconPtr, iconBytes.Length);
nint iconHandle = SDL_LoadBMP_RW(rwOpsStruct, 1);
SDL_SetWindowIcon(WindowHandle, iconHandle); SDL_SetWindowIcon(WindowHandle, iconHandle);
SDL_FreeSurface(iconHandle); SDL_DestroySurface(iconHandle);
}
} }
} }
@@ -181,18 +175,27 @@ namespace Ryujinx.Headless
Width = ExclusiveFullscreenWidth; Width = ExclusiveFullscreenWidth;
Height = ExclusiveFullscreenHeight; Height = ExclusiveFullscreenHeight;
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI; DefaultFlags = SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY;
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN; FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
} }
else if (IsFullscreen) else if (IsFullscreen)
{ {
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI; DefaultFlags = SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY;
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP; FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_BORDERLESS;
} }
WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), Width, Height, DefaultFlags | FullscreenFlag | GetWindowFlags()); SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}");
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId));
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId));
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, Width);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, Height);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, (long)(DefaultFlags | FullscreenFlag | WindowFlags));
if (WindowHandle == nint.Zero) WindowHandle = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
if (WindowHandle == null)
{ {
string errorMessage = $"SDL_CreateWindow failed with error \"{SDL_GetError()}\""; string errorMessage = $"SDL_CreateWindow failed with error \"{SDL_GetError()}\"";
@@ -204,16 +207,16 @@ namespace Ryujinx.Headless
SetWindowIcon(); SetWindowIcon();
_windowId = SDL_GetWindowID(WindowHandle); _windowId = SDL_GetWindowID(WindowHandle);
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent); SDL3Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
} }
private void HandleWindowEvent(SDL_Event evnt) private void HandleWindowEvent(SDL_Event evnt)
{ {
if (evnt.type == SDL_EventType.SDL_WINDOWEVENT) if ((uint)evnt.Type >= (uint)SDL_EventType.SDL_EVENT_WINDOW_FIRST && (uint)evnt.Type <= (uint)SDL_EventType.SDL_EVENT_WINDOW_LAST)
{ {
switch (evnt.window.windowEvent) switch (evnt.Type)
{ {
case SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_EventType.SDL_EVENT_WINDOW_RESIZED:
// Unlike on Windows, this event fires on macOS when triggering fullscreen mode. // Unlike on Windows, this event fires on macOS when triggering fullscreen mode.
// And promptly crashes the process because `Renderer?.window.SetSize` is undefined. // And promptly crashes the process because `Renderer?.window.SetSize` is undefined.
// As we don't need this to fire in either case we can test for fullscreen. // As we don't need this to fire in either case we can test for fullscreen.
@@ -226,7 +229,7 @@ namespace Ryujinx.Headless
} }
break; break;
case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE: case SDL_EventType.SDL_EVENT_WINDOW_CLOSE_REQUESTED:
Exit(); Exit();
break; break;
} }
@@ -245,7 +248,7 @@ namespace Ryujinx.Headless
protected abstract void SwapBuffers(); protected abstract void SwapBuffers();
public abstract SDL_WindowFlags GetWindowFlags(); public abstract SDL_WindowFlags WindowFlags { get; }
private string GetGpuDriverName() private string GetGpuDriverName()
{ {
@@ -421,7 +424,7 @@ namespace Ryujinx.Headless
// Get screen touch position // Get screen touch position
if (!_enableMouse) if (!_enableMouse)
{ {
hasTouch = TouchScreenManager.Update(true, ((SDL2MouseDriver)_inputManager.MouseDriver).IsButtonPressed(MouseButton.Button1), _aspectRatio.ToFloat()); hasTouch = TouchScreenManager.Update(true, ((SDL3MouseDriver)_inputManager.MouseDriver).IsButtonPressed(MouseButton.Button1), _aspectRatio.ToFloat());
} }
if (!hasTouch) if (!hasTouch)
@@ -473,7 +476,7 @@ namespace Ryujinx.Headless
public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText) public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText)
{ {
// SDL2 doesn't support input dialogs // SDL3 doesn't support input dialogs
userText = "Ryujinx"; userText = "Ryujinx";
return true; return true;
@@ -488,7 +491,7 @@ namespace Ryujinx.Headless
public bool DisplayCabinetDialog(out string userText) public bool DisplayCabinetDialog(out string userText)
{ {
// SDL2 doesn't support input dialogs // SDL3 doesn't support input dialogs
userText = "Ryujinx"; userText = "Ryujinx";
return true; return true;
@@ -528,25 +531,34 @@ namespace Ryujinx.Headless
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText) public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
{ {
SDL_MessageBoxData data = new() SDL_MessageBoxButtonData[] buttons = new SDL_MessageBoxButtonData[buttonsText.Length];
{
title = title,
message = message,
buttons = new SDL_MessageBoxButtonData[buttonsText.Length],
numbuttons = buttonsText.Length,
window = WindowHandle,
};
for (int i = 0; i < buttonsText.Length; i++) for (int i = 0; i < buttonsText.Length; i++)
{ {
data.buttons[i] = new SDL_MessageBoxButtonData string buttonText = buttonsText[i];
{ fixed (byte* pButtonText = &buttonText.ToBytes()[0])
buttonid = i, buttons[i] = new SDL_MessageBoxButtonData
text = buttonsText[i], {
}; buttonID = i,
text = pButtonText,
};
} }
SDL_ShowMessageBox(ref data, out int _); fixed (byte* pTitle = &title.ToBytes()[0])
fixed (byte* pMessage = &message.ToBytes()[0])
fixed (SDL_MessageBoxButtonData* p = &buttons[0]) {
SDL_MessageBoxData data = new()
{
title = pTitle,
message = pMessage,
buttons = p,
numbuttons = buttonsText.Length,
window = WindowHandle
};
SDL_ShowMessageBox(&data, null);
}
return true; return true;
} }
@@ -564,11 +576,11 @@ namespace Ryujinx.Headless
TouchScreenManager?.Dispose(); TouchScreenManager?.Dispose();
NpadManager.Dispose(); NpadManager.Dispose();
SDL2Driver.Instance.UnregisterWindow(_windowId); SDL3Driver.Instance.UnregisterWindow(_windowId);
SDL_DestroyWindow(WindowHandle); SDL_DestroyWindow(WindowHandle);
SDL2Driver.Instance.Dispose(); SDL3Driver.Instance.Dispose();
} }
} }
+3 -3
View File
@@ -10,7 +10,7 @@ using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Headless; using Ryujinx.Headless;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.SDL2.Common; using Ryujinx.SDL3.Common;
using Ryujinx.UI.Common; using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI.Common.Helper;
@@ -118,8 +118,8 @@ namespace Ryujinx.Ava
// Initialize Discord integration. // Initialize Discord integration.
DiscordIntegrationModule.Initialize(); DiscordIntegrationModule.Initialize();
// Initialize SDL2 driver // Initialize SDL3 driver
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input); SDL3Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
ReloadConfig(); ReloadConfig();
+2 -2
View File
@@ -57,11 +57,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" /> <ProjectReference Include="..\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<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.SDL3\Ryujinx.Input.SDL3.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.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" />
@@ -491,7 +491,7 @@ namespace Ryujinx.Ava.UI.Models.Input
var config = new StandardControllerInputConfig var config = new StandardControllerInputConfig
{ {
Id = Id, Id = Id,
Backend = InputBackendType.GamepadSDL2, Backend = InputBackendType.GamepadSDL3,
PlayerIndex = PlayerIndex, PlayerIndex = PlayerIndex,
ControllerType = ControllerType, ControllerType = ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId> LeftJoycon = new LeftJoyconCommonConfig<GamepadInputId>
@@ -617,7 +617,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
config = new StandardControllerInputConfig config = new StandardControllerInputConfig
{ {
Version = InputConfig.CurrentVersion, Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2, Backend = InputBackendType.GamepadSDL3,
Id = id, Id = id,
ControllerType = ControllerType.ProController, ControllerType = ControllerType.ProController,
DeadzoneLeft = 0.1f, DeadzoneLeft = 0.1f,
@@ -3,7 +3,7 @@ using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.OpenAL; using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2; using Ryujinx.Audio.Backends.SDL3;
using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Audio.Backends.SoundIo;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
@@ -248,7 +248,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableDebug { get; set; } public bool EnableDebug { get; set; }
public bool IsOpenAlEnabled { get; set; } public bool IsOpenAlEnabled { get; set; }
public bool IsSoundIoEnabled { get; set; } public bool IsSoundIoEnabled { get; set; }
public bool IsSDL2Enabled { get; set; } public bool IsSDL3Enabled { get; set; }
public bool IsCustomResolutionScaleActive => _resolutionScale == 4; public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr; public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
@@ -427,13 +427,13 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported; IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported;
IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported; IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported;
IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported; IsSDL3Enabled = SDL3HardwareDeviceDriver.IsSupported;
await Dispatcher.UIThread.InvokeAsync(() => await Dispatcher.UIThread.InvokeAsync(() =>
{ {
OnPropertyChanged(nameof(IsOpenAlEnabled)); OnPropertyChanged(nameof(IsOpenAlEnabled));
OnPropertyChanged(nameof(IsSoundIoEnabled)); OnPropertyChanged(nameof(IsSoundIoEnabled));
OnPropertyChanged(nameof(IsSDL2Enabled)); OnPropertyChanged(nameof(IsSDL3Enabled));
}); });
} }
@@ -43,8 +43,8 @@
IsEnabled="{Binding IsSoundIoEnabled}" IsEnabled="{Binding IsSoundIoEnabled}"
Content="{locale:Locale SettingsTabSystemAudioBackendSoundIO}" /> Content="{locale:Locale SettingsTabSystemAudioBackendSoundIO}" />
<ComboBoxItem <ComboBoxItem
IsEnabled="{Binding IsSDL2Enabled}" IsEnabled="{Binding IsSDL3Enabled}"
Content="{locale:Locale SettingsTabSystemAudioBackendSDL2}" /> Content="{locale:Locale SettingsTabSystemAudioBackendSDL3}" />
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+2 -2
View File
@@ -19,7 +19,7 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2; using Ryujinx.Input.SDL3;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.UI.App.Common; using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common; using Ryujinx.UI.Common;
@@ -107,7 +107,7 @@ namespace Ryujinx.Ava.UI.Windows
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver()); InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL3GamepadDriver());
this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged); this.GetObservable(IsActiveProperty).Subscribe(IsActiveChanged);
this.ScalingChanged += OnScalingChanged; this.ScalingChanged += OnScalingChanged;