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-04-14 22:13:05 -05:00
co-authored by Maki
parent 7aa27b2ca5
commit 251b65771a
54 changed files with 893 additions and 878 deletions
+4 -4
View File
@@ -1,6 +1,6 @@
using DiscordRPC;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.SDL2;
using Ryujinx.Audio.Backends.SDL3;
using Ryujinx.Ava;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
@@ -159,7 +159,7 @@ namespace Ryujinx.Headless
config = new StandardControllerInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.GamepadSDL2,
Backend = InputBackendType.GamepadSDL3,
Id = null,
ControllerType = ControllerType.JoyconPair,
DeadzoneLeft = 0.1f,
@@ -308,7 +308,7 @@ namespace Ryujinx.Headless
return new VulkanRenderer(
api,
(instance, _) => new SurfaceKHR((ulong)(vulkanWindow.CreateWindowSurface(instance.Handle))),
vulkanWindow.GetRequiredInstanceExtensions,
VulkanWindow.GetRequiredInstanceExtensions,
preferredGpuId);
}
@@ -332,7 +332,7 @@ namespace Ryujinx.Headless
_accountManager,
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),
new SDL3HardwareDeviceDriver(),
options.DramSize,
window,
options.SystemLanguage,
+6 -5
View File
@@ -19,9 +19,10 @@ using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.SDL2.Common;
using Ryujinx.Input.SDL3;
using Ryujinx.SDL3.Common;
using Ryujinx.UI.Common.Configuration;
using SDL;
using System;
using System.Collections.Generic;
using System.IO;
@@ -58,7 +59,7 @@ namespace Ryujinx.Headless
AutoResetEvent invoked = new(false);
// MacOS must perform SDL polls from the main thread.
SDL2Driver.MainThreadDispatcher = action =>
SDL3Driver.MainThreadDispatcher = action =>
{
invoked.Reset();
@@ -169,7 +170,7 @@ namespace Ryujinx.Headless
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient, option.UserProfile);
_userChannelPersistence = new UserChannelPersistence();
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
_inputManager = new InputManager(new SDL3KeyboardDriver(), new SDL3GamepadDriver());
GraphicsConfig.EnableShaderCache = !option.DisableShaderCache;
@@ -368,7 +369,7 @@ namespace Ryujinx.Headless
_window = window;
_window.IsFullscreen = options.IsFullscreen;
_window.DisplayId = options.DisplayId;
_window.DisplayId = (SDL_DisplayID)options.DisplayId;
_window.IsExclusiveFullscreen = options.IsExclusiveFullscreen;
_window.ExclusiveFullscreenWidth = options.ExclusiveFullscreenWidth;
_window.ExclusiveFullscreenHeight = options.ExclusiveFullscreenHeight;
+42 -40
View File
@@ -4,16 +4,17 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Input.HLE;
using SDL;
using System;
using static SDL2.SDL;
using static SDL.SDL3;
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()}");
}
@@ -21,21 +22,21 @@ namespace Ryujinx.Headless
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_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_FLAGS, debugLevel != GraphicsDebugLevel.None ? (int)SDL_GLcontext.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_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_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_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_ACCELERATED_VISUAL, 1));
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_BLUE_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_STENCIL_SIZE, 0));
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_ACCELERATED_VISUAL, 1));
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_BLUE_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_STENCIL_SIZE, 0));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_DOUBLEBUFFER, 1));
CheckResult(SDL_GL_SetAttribute(SDL_GLAttr.SDL_GL_STEREO, 0));
}
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 nint _window;
private readonly SDL_GLContextState* _context;
private readonly SDL_Window* _window;
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;
_window = window;
_shouldDisposeWindow = shouldDisposeWindow;
}
public static SDL2OpenGLContext CreateBackgroundContext(SDL2OpenGLContext sharedContext)
public static SDL3OpenGLContext CreateBackgroundContext(SDL3OpenGLContext sharedContext)
{
sharedContext.MakeCurrent();
// Ensure we share our contexts.
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);
nint context = SDL_GL_CreateContext(windowHandle);
SDL_Window* windowHandle = SDL_CreateWindow("Ryujinx background context window", 1, 1, SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_HIDDEN);
SDL_GLContextState* context = SDL_GL_CreateContext(windowHandle);
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()
@@ -84,9 +85,9 @@ namespace Ryujinx.Headless
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()}\"";
@@ -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()
{
SDL_GL_DeleteContext(_context);
SDL_GL_DestroyContext (_context);
if (_shouldDisposeWindow)
{
@@ -110,7 +111,7 @@ namespace Ryujinx.Headless
}
private readonly GraphicsDebugLevel _glLogLevel;
private SDL2OpenGLContext _openGLContext;
private SDL3OpenGLContext _openGLContext;
public OpenGLWindow(
InputManager inputManager,
@@ -124,16 +125,16 @@ namespace Ryujinx.Headless
_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()
{
// Ensure to not share this context with other contexts before this point.
SetupOpenGLAttributes(false, _glLogLevel);
nint context = SDL_GL_CreateContext(WindowHandle);
SDL_GLContextState* context = SDL_GL_CreateContext(WindowHandle);
CheckResult(SDL_GL_SetSwapInterval(1));
if (context == nint.Zero)
if (context == null)
{
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.
_openGLContext = new SDL2OpenGLContext(context, WindowHandle, false);
_openGLContext = new SDL3OpenGLContext(context, WindowHandle, false);
// First take exclusivity on the OpenGL context.
((OpenGLRenderer)Renderer).InitializeBackgroundContext(SDL2OpenGLContext.CreateBackgroundContext(_openGLContext));
((OpenGLRenderer)Renderer).InitializeBackgroundContext(SDL3OpenGLContext.CreateBackgroundContext(_openGLContext));
_openGLContext.MakeCurrent();
@@ -162,7 +163,8 @@ namespace Ryujinx.Headless
else if (IsFullscreen)
{
// 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()}");
@@ -191,7 +193,7 @@ namespace Ryujinx.Headless
Device.DisposeGpu();
// Unbind context and destroy everything
CheckResult(SDL_GL_MakeCurrent(WindowHandle, nint.Zero));
CheckResult(SDL_GL_MakeCurrent(WindowHandle, null));
_openGLContext.Dispose();
}
+23 -38
View File
@@ -1,17 +1,16 @@
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Input.HLE;
using Ryujinx.SDL2.Common;
using Ryujinx.SDL3.Common;
using SDL;
using System;
using System.Runtime.InteropServices;
using static SDL2.SDL;
using static SDL.SDL3;
namespace Ryujinx.Headless
{
class VulkanWindow : WindowBase
{
private readonly GraphicsDebugLevel _glLogLevel;
public VulkanWindow(
InputManager inputManager,
GraphicsDebugLevel glLogLevel,
@@ -21,10 +20,9 @@ namespace Ryujinx.Headless
bool 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() { }
@@ -42,18 +40,15 @@ namespace Ryujinx.Headless
}
}
private static void BasicInvoke(Action action)
public unsafe nint CreateWindowSurface(nint instance)
{
action();
}
public nint CreateWindowSurface(nint instance)
{
ulong surfaceHandle = 0;
VkSurfaceKHR_T surface = new();
VkSurfaceKHR_T* surfaceHandle = &surface;
VkSurfaceKHR_T** surfaceHandleHandle = &surfaceHandle;
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()}\"";
@@ -63,9 +58,9 @@ namespace Ryujinx.Headless
}
}
if (SDL2Driver.MainThreadDispatcher != null)
if (SDL3Driver.MainThreadDispatcher != null)
{
SDL2Driver.MainThreadDispatcher(CreateSurface);
SDL3Driver.MainThreadDispatcher(CreateSurface);
}
else
{
@@ -75,32 +70,22 @@ namespace Ryujinx.Headless
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)
{
nint[] rawExtensions = new nint[(int)extensionsCount];
string[] extensions = new string[(int)extensionsCount];
uint extensionCount = 0;
byte** extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount);
if (extensionCount == 0) {
string errorMessage = $"SDL_Vulkan_GetInstanceExtensions failed with error \"{SDL_GetError()}\"";
fixed (nint* rawExtensionsPtr = rawExtensions)
{
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]);
}
Logger.Error?.Print(LogClass.Application, errorMessage);
return extensions;
}
}
throw new Exception(errorMessage);
}
string errorMessage = $"SDL_Vulkan_GetInstanceExtensions failed with error \"{SDL_GetError()}\"";
Logger.Error?.Print(LogClass.Application, errorMessage);
throw new Exception(errorMessage);
string[] extensionArr = new string[extensionCount];
for (int i = 0; i < extensionCount; i++) {
extensionArr[i] = Marshal.PtrToStringUTF8((nint)extensions[i]);
}
return extensionArr;
}
protected override void FinalizeWindowRenderer()
+66 -54
View File
@@ -1,3 +1,4 @@
using LibHac.Util;
using Ryujinx.Ava;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
@@ -12,36 +13,32 @@ using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationPr
using Ryujinx.HLE.UI;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
using Ryujinx.Input.SDL2;
using Ryujinx.SDL2.Common;
using Ryujinx.Input.SDL3;
using Ryujinx.SDL3.Common;
using SDL;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using static SDL2.SDL;
using static SDL.SDL3;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
using ScalingFilter = Ryujinx.Common.Configuration.ScalingFilter;
using Switch = Ryujinx.HLE.Switch;
namespace Ryujinx.Headless
{
abstract partial class WindowBase : IHostUIHandler, IDisposable
abstract unsafe class WindowBase : IHostUIHandler, IDisposable
{
protected const int DefaultWidth = 1280;
protected const int DefaultHeight = 720;
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 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)
{
_mainThreadActions.Enqueue(action);
@@ -54,12 +51,12 @@ namespace Ryujinx.Headless
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
protected nint WindowHandle { get; set; }
protected SDL_Window* WindowHandle { get; set; }
public IHostUITheme HostUITheme { get; }
public int Width { 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 IsExclusiveFullscreen { get; set; }
public int ExclusiveFullscreenWidth { get; set; }
@@ -68,7 +65,7 @@ namespace Ryujinx.Headless
public ScalingFilter ScalingFilter { get; set; }
public int ScalingFilterLevel { get; set; }
protected SDL2MouseDriver MouseDriver;
protected SDL3MouseDriver MouseDriver;
private readonly InputManager _inputManager;
private readonly IKeyboard _keyboardInterface;
private readonly GraphicsDebugLevel _glLogLevel;
@@ -81,7 +78,7 @@ namespace Ryujinx.Headless
private long _ticks;
private bool _isActive;
private bool _isStopped;
private uint _windowId;
private SDL_WindowID _windowId;
private string _gpuDriverName;
@@ -97,7 +94,7 @@ namespace Ryujinx.Headless
HideCursorMode hideCursorMode,
bool ignoreControllerApplet)
{
MouseDriver = new SDL2MouseDriver(hideCursorMode);
MouseDriver = new SDL3MouseDriver(hideCursorMode);
_inputManager = inputManager;
_inputManager.SetMouseDriver(MouseDriver);
NpadManager = _inputManager.CreateNpadManager();
@@ -114,7 +111,7 @@ namespace Ryujinx.Headless
_ignoreControllerApplet = ignoreControllerApplet;
HostUITheme = new HeadlessHostUiTheme();
SDL2Driver.Instance.Initialize();
SDL3Driver.Instance.Initialize();
}
public void Initialize(Switch device, List<InputConfig> inputConfigs, bool enableKeyboard, bool enableMouse)
@@ -149,16 +146,13 @@ namespace Ryujinx.Headless
iconStream.Close();
unsafe
fixed (byte* iconPtr = iconBytes)
{
fixed (byte* iconPtr = iconBytes)
{
nint rwOpsStruct = SDL_RWFromConstMem((nint)iconPtr, iconBytes.Length);
nint iconHandle = SDL_LoadBMP_RW(rwOpsStruct, 1);
SDL_IOStream* rwOpsStruct = SDL_IOFromConstMem((nint)iconPtr, (UIntPtr)iconBytes.Length);
SDL_Surface* iconHandle = SDL_LoadBMP_IO(rwOpsStruct, true);
SDL_SetWindowIcon(WindowHandle, iconHandle);
SDL_FreeSurface(iconHandle);
}
SDL_SetWindowIcon(WindowHandle, iconHandle);
SDL_DestroySurface(iconHandle);
}
}
@@ -181,18 +175,27 @@ namespace Ryujinx.Headless
Width = ExclusiveFullscreenWidth;
Height = ExclusiveFullscreenHeight;
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY;
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
}
else if (IsFullscreen)
{
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
DefaultFlags = SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY;
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()}\"";
@@ -204,16 +207,16 @@ namespace Ryujinx.Headless
SetWindowIcon();
_windowId = SDL_GetWindowID(WindowHandle);
SDL2Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
SDL3Driver.Instance.RegisterWindow(_windowId, HandleWindowEvent);
}
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.
// 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.
@@ -226,7 +229,7 @@ namespace Ryujinx.Headless
}
break;
case SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE:
case SDL_EventType.SDL_EVENT_WINDOW_CLOSE_REQUESTED:
Exit();
break;
}
@@ -245,7 +248,7 @@ namespace Ryujinx.Headless
protected abstract void SwapBuffers();
public abstract SDL_WindowFlags GetWindowFlags();
public abstract SDL_WindowFlags WindowFlags { get; }
private string GetGpuDriverName()
{
@@ -421,7 +424,7 @@ namespace Ryujinx.Headless
// Get screen touch position
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)
@@ -473,7 +476,7 @@ namespace Ryujinx.Headless
public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText)
{
// SDL2 doesn't support input dialogs
// SDL3 doesn't support input dialogs
userText = "Ryujinx";
return true;
@@ -488,7 +491,7 @@ namespace Ryujinx.Headless
public bool DisplayCabinetDialog(out string userText)
{
// SDL2 doesn't support input dialogs
// SDL3 doesn't support input dialogs
userText = "Ryujinx";
return true;
@@ -528,25 +531,34 @@ namespace Ryujinx.Headless
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
{
SDL_MessageBoxData data = new()
{
title = title,
message = message,
buttons = new SDL_MessageBoxButtonData[buttonsText.Length],
numbuttons = buttonsText.Length,
window = WindowHandle,
};
SDL_MessageBoxButtonData[] buttons = new SDL_MessageBoxButtonData[buttonsText.Length];
for (int i = 0; i < buttonsText.Length; i++)
{
data.buttons[i] = new SDL_MessageBoxButtonData
{
buttonid = i,
text = buttonsText[i],
};
string buttonText = buttonsText[i];
fixed (byte* pButtonText = &buttonText.ToBytes()[0])
buttons[i] = new SDL_MessageBoxButtonData
{
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;
}
@@ -564,11 +576,11 @@ namespace Ryujinx.Headless
TouchScreenManager?.Dispose();
NpadManager.Dispose();
SDL2Driver.Instance.UnregisterWindow(_windowId);
SDL3Driver.Instance.UnregisterWindow(_windowId);
SDL_DestroyWindow(WindowHandle);
SDL2Driver.Instance.Dispose();
SDL3Driver.Instance.Dispose();
}
}