Explorar o código

UI: Add Metal surface creation for MoltenVK (#3980)

* Initial implementation of metal surface across UIs

* Fix SDL2 on windows

* Update Ryujinx/Ryujinx.csproj

Co-authored-by: Mary-nyan <thog@protonmail.com>

* Address Feedback

Co-authored-by: Mary-nyan <thog@protonmail.com>
riperiperi %!s(int64=3) %!d(string=hai) anos
pai
achega
e211c3f00a
Modificáronse 31 ficheiros con 495 adicións e 63 borrados
  1. 1 1
      Ryujinx.Ava/AppHost.cs
  2. 127 0
      Ryujinx.Ava/Helper/MetalHelper.cs
  3. 5 4
      Ryujinx.Ava/Program.cs
  4. 4 2
      Ryujinx.Ava/Ryujinx.Ava.csproj
  5. 31 2
      Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs
  6. 5 0
      Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs
  7. 2 0
      Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs
  8. 6 0
      Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs
  9. 1 1
      Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml
  10. 8 1
      Ryujinx.Common/Configuration/AppDataManager.cs
  11. 6 6
      Ryujinx.Graphics.Vulkan/VulkanInitialization.cs
  12. BIN=BIN
      Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png
  13. 4 4
      Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs
  14. 2 0
      Ryujinx.HLE/Ryujinx.HLE.csproj
  15. 20 0
      Ryujinx.Headless.SDL2/Program.cs
  16. 2 1
      Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
  17. 24 4
      Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs
  18. 26 8
      Ryujinx.Headless.SDL2/WindowBase.cs
  19. 2 1
      Ryujinx.Memory/MemoryManagerUnixHelper.cs
  20. 1 1
      Ryujinx/Modules/Updater/UpdateDialog.cs
  21. 36 0
      Ryujinx/Program.cs
  22. 7 8
      Ryujinx/Ryujinx.csproj
  23. 134 0
      Ryujinx/Ui/Helper/MetalHelper.cs
  24. 2 5
      Ryujinx/Ui/MainWindow.cs
  25. 14 1
      Ryujinx/Ui/VKRenderer.cs
  26. 1 1
      Ryujinx/Ui/Widgets/ProfileDialog.cs
  27. 1 1
      Ryujinx/Ui/Windows/CheatWindow.cs
  28. 10 7
      Ryujinx/Ui/Windows/ControllerWindow.cs
  29. 1 1
      Ryujinx/Ui/Windows/DlcWindow.cs
  30. 11 2
      Ryujinx/Ui/Windows/SettingsWindow.cs
  31. 1 1
      Ryujinx/Ui/Windows/TitleUpdateWindow.cs

+ 1 - 1
Ryujinx.Ava/AppHost.cs

@@ -125,7 +125,7 @@ namespace Ryujinx.Ava
             _inputManager = inputManager;
             _accountManager = accountManager;
             _userChannelPersistence = userChannelPersistence;
-            _renderingThread = new Thread(RenderLoop) { Name = "GUI.RenderThread" };
+            _renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
             _hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
             _lastCursorMoveTime = Stopwatch.GetTimestamp();
             _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;

+ 127 - 0
Ryujinx.Ava/Helper/MetalHelper.cs

@@ -0,0 +1,127 @@
+using System;
+using System.Runtime.Versioning;
+using System.Runtime.InteropServices;
+using Avalonia;
+
+namespace Ryujinx.Ava.Ui.Helper
+{
+    public delegate void UpdateBoundsCallbackDelegate(Rect rect);
+
+    [SupportedOSPlatform("macos")]
+    static class MetalHelper
+    {
+        private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";
+
+        private struct Selector
+        {
+            public readonly IntPtr NativePtr;
+
+            public unsafe Selector(string value)
+            {
+                int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
+                byte* data = stackalloc byte[size];
+
+                fixed (char* pValue = value)
+                {
+                    System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
+                }
+
+                NativePtr = sel_registerName(data);
+            }
+
+            public static implicit operator Selector(string value) => new Selector(value);
+        }
+
+        private static unsafe IntPtr GetClass(string value)
+        {
+            int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
+            byte* data = stackalloc byte[size];
+
+            fixed (char* pValue = value)
+            {
+                System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
+            }
+
+            return objc_getClass(data);
+        }
+
+        private struct NSPoint
+        {
+            public double X;
+            public double Y;
+
+            public NSPoint(double x, double y)
+            {
+                X = x;
+                Y = y;
+            }
+        }
+
+        private struct NSRect
+        {
+            public NSPoint Pos;
+            public NSPoint Size;
+
+            public NSRect(double x, double y, double width, double height)
+            {
+                Pos = new NSPoint(x, y);
+                Size = new NSPoint(width, height);
+            }
+        }
+
+        public static IntPtr GetMetalLayer(out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds)
+        {
+            // Create a new CAMetalLayer.
+            IntPtr layerClass = GetClass("CAMetalLayer");
+            IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc");
+            objc_msgSend(metalLayer, "init");
+
+            // Create a child NSView to render into.
+            IntPtr nsViewClass = GetClass("NSView");
+            IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc");
+            objc_msgSend(child, "init", new NSRect(0, 0, 0, 0));
+
+            // Make its renderer our metal layer.
+            objc_msgSend(child, "setWantsLayer:", (byte)1);
+            objc_msgSend(child, "setLayer:", metalLayer);
+            objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
+
+            // Ensure the scale factor is up to date.
+            updateBounds = (Rect rect) => {
+                objc_msgSend(metalLayer, "setContentsScale:", Program.DesktopScaleFactor);
+            };
+
+            nsView = child;
+            return metalLayer;
+        }
+
+        public static void DestroyMetalLayer(IntPtr nsView, IntPtr metalLayer)
+        {
+            // TODO
+        }
+
+        [DllImport(LibObjCImport)]
+        private static unsafe extern IntPtr sel_registerName(byte* data);
+
+        [DllImport(LibObjCImport)]
+        private static unsafe extern IntPtr objc_getClass(byte* data);
+
+        [DllImport(LibObjCImport)]
+        private static extern void objc_msgSend(IntPtr receiver, Selector selector);
+
+        [DllImport(LibObjCImport)]
+        private static extern void objc_msgSend(IntPtr receiver, Selector selector, byte value);
+
+        [DllImport(LibObjCImport)]
+        private static extern void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
+
+        [DllImport(LibObjCImport)]
+        private static extern void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
+
+        [DllImport(LibObjCImport)]
+        private static extern void objc_msgSend(IntPtr receiver, Selector selector, double value);
+
+        [DllImport(LibObjCImport, EntryPoint = "objc_msgSend")]
+        private static extern IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
+    }
+}

+ 5 - 4
Ryujinx.Ava/Program.cs

@@ -22,10 +22,11 @@ namespace Ryujinx.Ava
 {
     internal class Program
     {
-        public static double WindowScaleFactor { get; set; }
-        public static string Version           { get; private set; }
-        public static string ConfigurationPath { get; private set; }
-        public static bool   PreviewerDetached { get; private set; }
+        public static double WindowScaleFactor  { get; set; }
+        public static double DesktopScaleFactor { get; set; } = 1.0;
+        public static string Version            { get; private set; }
+        public static string ConfigurationPath  { get; private set; }
+        public static bool   PreviewerDetached {  get; private set; }
 
         [DllImport("user32.dll", SetLastError = true)]
         public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);

+ 4 - 2
Ryujinx.Ava/Ryujinx.Ava.csproj

@@ -31,8 +31,10 @@
     <PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
 
     <PackageReference Include="OpenTK.Core" Version="4.7.5" />
-    <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
-    <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
+    <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
+    <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
+    <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.osx" Version="5.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
+    <PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
     <PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
     <PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
     <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />

+ 31 - 2
Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs

@@ -2,11 +2,10 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Platform;
+using Ryujinx.Ava.Ui.Helper;
 using SPB.Graphics;
 using SPB.Platform;
 using SPB.Platform.GLX;
-using SPB.Platform.X11;
-using SPB.Windowing;
 using System;
 using System.Runtime.InteropServices;
 using System.Runtime.Versioning;
@@ -23,6 +22,10 @@ namespace Ryujinx.Ava.Ui.Controls
         protected GLXWindow X11Window { get; set; }
         protected IntPtr WindowHandle { get; set; }
         protected IntPtr X11Display { get; set; }
+        protected IntPtr NsView { get; set; }
+        protected IntPtr MetalLayer { get; set; }
+
+        private UpdateBoundsCallbackDelegate _updateBoundsCallback;
 
         public event EventHandler<IntPtr> WindowCreated;
         public event EventHandler<Size> SizeChanged;
@@ -58,6 +61,7 @@ namespace Ryujinx.Ava.Ui.Controls
         private void StateChanged(Rect rect)
         {
             SizeChanged?.Invoke(this, rect.Size);
+            _updateBoundsCallback?.Invoke(rect);
         }
 
         protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
@@ -70,6 +74,11 @@ namespace Ryujinx.Ava.Ui.Controls
             {
                 return CreateWin32(parent);
             }
+            else if (OperatingSystem.IsMacOS())
+            {
+                return CreateMacOs(parent);
+            }
+
             return base.CreateNativeControlCore(parent);
         }
 
@@ -85,6 +94,10 @@ namespace Ryujinx.Ava.Ui.Controls
             {
                 DestroyWin32(control);
             }
+            else if (OperatingSystem.IsMacOS())
+            {
+                DestroyMacOS();
+            }
             else
             {
                 base.DestroyNativeControlCore(control);
@@ -187,6 +200,16 @@ namespace Ryujinx.Ava.Ui.Controls
             return DefWindowProc(hWnd, msg, (IntPtr)wParam, (IntPtr)lParam);
         }
 
+        [SupportedOSPlatform("macos")]
+        IPlatformHandle CreateMacOs(IPlatformHandle parent)
+        {
+            MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
+
+            NsView = nsView;
+
+            return new PlatformHandle(nsView, "NSView");
+        }
+
         void DestroyLinux()
         {
             X11Window?.Dispose();
@@ -198,5 +221,11 @@ namespace Ryujinx.Ava.Ui.Controls
             DestroyWindow(handle.Handle);
             UnregisterClass(_className, GetModuleHandle(null));
         }
+
+        [SupportedOSPlatform("macos")]
+        void DestroyMacOS()
+        {
+            MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
+        }
     }
 }

+ 5 - 0
Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs

@@ -3,6 +3,7 @@ using Ryujinx.Ava.Ui.Controls;
 using Silk.NET.Vulkan;
 using SPB.Graphics.Vulkan;
 using SPB.Platform.GLX;
+using SPB.Platform.Metal;
 using SPB.Platform.Win32;
 using SPB.Platform.X11;
 using SPB.Windowing;
@@ -37,6 +38,10 @@ namespace Ryujinx.Ava.Ui
             {
                 _window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
             }
+            else if (OperatingSystem.IsMacOS())
+            {
+                _window = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
+            }
             else
             {
                 throw new PlatformNotSupportedException();

+ 2 - 0
Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs

@@ -108,6 +108,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
             }
         }
 
+        public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
+
         public bool DirectoryChanged
         {
             get => _directoryChanged;

+ 6 - 0
Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs

@@ -154,6 +154,12 @@ namespace Ryujinx.Ava.Ui.Windows
             }
         }
 
+        protected override void HandleScalingChanged(double scale)
+        {
+            Program.DesktopScaleFactor = scale;
+            base.HandleScalingChanged(scale);
+        }
+
         public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
         {
             if (args.Application != null)

+ 1 - 1
Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml

@@ -540,7 +540,7 @@
                                     <ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
                                         <TextBlock Text="Vulkan" />
                                     </ComboBoxItem>
-                                    <ComboBoxItem>
+                                    <ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
                                         <TextBlock Text="OpenGL" />
                                     </ComboBoxItem>
                                 </ComboBox>

+ 8 - 1
Ryujinx.Common/Configuration/AppDataManager.cs

@@ -45,7 +45,14 @@ namespace Ryujinx.Common.Configuration
 
         public static void Initialize(string baseDirPath)
         {
-            string userProfilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
+            string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+
+            if (appDataPath.Length == 0)
+            {
+                appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+            }
+
+            string userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
             string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
 
             if (Directory.Exists(portablePath))

+ 6 - 6
Ryujinx.Graphics.Vulkan/VulkanInitialization.cs

@@ -1,4 +1,4 @@
-using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using Ryujinx.Graphics.GAL;
 using Silk.NET.Vulkan;
@@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Vulkan
         {
             ExtConditionalRendering.ExtensionName,
             ExtExtendedDynamicState.ExtensionName,
+            ExtTransformFeedback.ExtensionName,
             KhrDrawIndirectCount.ExtensionName,
             KhrPushDescriptor.ExtensionName,
             "VK_EXT_custom_border_color",
@@ -36,8 +37,7 @@ namespace Ryujinx.Graphics.Vulkan
 
         public static string[] RequiredExtensions { get; } = new string[]
         {
-            KhrSwapchain.ExtensionName,
-            ExtTransformFeedback.ExtensionName
+            KhrSwapchain.ExtensionName
         };
 
         private static string[] _excludedMessages = new string[]
@@ -382,12 +382,12 @@ namespace Ryujinx.Graphics.Vulkan
                 DepthClamp = true,
                 DualSrcBlend = true,
                 FragmentStoresAndAtomics = true,
-                GeometryShader = true,
+                GeometryShader = supportedFeatures.GeometryShader,
                 ImageCubeArray = true,
                 IndependentBlend = true,
-                LogicOp = true,
+                LogicOp = supportedFeatures.LogicOp,
                 MultiViewport = true,
-                PipelineStatisticsQuery = true,
+                PipelineStatisticsQuery = supportedFeatures.PipelineStatisticsQuery,
                 SamplerAnisotropy = true,
                 ShaderClipDistance = true,
                 ShaderFloat64 = supportedFeatures.ShaderFloat64,

BIN=BIN
Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png


+ 4 - 4
Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs

@@ -11,7 +11,6 @@ using System.Numerics;
 using System.Reflection;
 using System.Runtime.InteropServices;
 using SixLabors.ImageSharp.PixelFormats;
-using Ryujinx.Common;
 
 namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
 {
@@ -68,8 +67,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
         {
             int ryujinxLogoSize = 32;
 
-            Stream logoStream = EmbeddedResources.GetStream("Ryujinx.Ui.Common/Resources/Logo_Ryujinx.png");
-            _ryujinxLogo = LoadResource(logoStream, ryujinxLogoSize, ryujinxLogoSize);
+            string ryujinxIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Logo_Ryujinx.png";
+            _ryujinxLogo = LoadResource(Assembly.GetExecutingAssembly(), ryujinxIconPath, ryujinxLogoSize, ryujinxLogoSize);
 
             string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
             string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
@@ -117,7 +116,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
                 uiThemeFontFamily,
                 "Liberation Sans",
                 "FreeSans",
-                "DejaVu Sans"
+                "DejaVu Sans",
+                "Lucida Grande"
             };
 
             foreach (string fontFamily in availableFonts)

+ 2 - 0
Ryujinx.HLE/Ryujinx.HLE.csproj

@@ -36,6 +36,7 @@
 
   <ItemGroup>
     <None Remove="Homebrew.npdm" />
+    <None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
     <None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
     <None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
     <None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />
@@ -44,6 +45,7 @@
 
   <ItemGroup>
     <EmbeddedResource Include="Homebrew.npdm" />
+    <EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
     <EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
     <EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
     <EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />

+ 20 - 0
Ryujinx.Headless.SDL2/Program.cs

@@ -77,6 +77,26 @@ namespace Ryujinx.Headless.SDL2
             _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient);
             _userChannelPersistence = new UserChannelPersistence();
 
+            if (OperatingSystem.IsMacOS())
+            {
+                AutoResetEvent invoked = new AutoResetEvent(false);
+
+                // MacOS must perform SDL polls from the main thread.
+                Ryujinx.SDL2.Common.SDL2Driver.MainThreadDispatcher = (Action action) =>
+                {
+                    invoked.Reset();
+
+                    WindowBase.QueueMainThreadAction(() =>
+                    {
+                        action();
+
+                        invoked.Set();
+                    });
+
+                    invoked.WaitOne();
+                };
+            }
+
             _inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
 
             GraphicsConfig.EnableShaderCache = true;

+ 2 - 1
Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj

@@ -12,7 +12,8 @@
 
   <ItemGroup>
     <PackageReference Include="OpenTK.Core" Version="4.7.5" />
-    <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
+    <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
+    <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.osx" Version="5.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
   </ItemGroup>
 
   <ItemGroup>

+ 24 - 4
Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs

@@ -1,6 +1,7 @@
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using Ryujinx.Input.HLE;
+using Ryujinx.SDL2.Common;
 using System;
 using System.Runtime.InteropServices;
 using static SDL2.SDL;
@@ -26,15 +27,34 @@ namespace Ryujinx.Headless.SDL2.Vulkan
             MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
         }
 
+        private void BasicInvoke(Action action)
+        {
+            action();
+        }
+
         public unsafe IntPtr CreateWindowSurface(IntPtr instance)
         {
-            if (SDL_Vulkan_CreateSurface(WindowHandle, instance, out ulong surfaceHandle) == SDL_bool.SDL_FALSE)
+            ulong surfaceHandle = 0;
+
+            Action createSurface = () =>
             {
-                string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\"";
+                if (SDL_Vulkan_CreateSurface(WindowHandle, instance, out surfaceHandle) == SDL_bool.SDL_FALSE)
+                {
+                    string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\"";
 
-                Logger.Error?.Print(LogClass.Application, errorMessage);
+                    Logger.Error?.Print(LogClass.Application, errorMessage);
 
-                throw new Exception(errorMessage);
+                    throw new Exception(errorMessage);
+                }
+            };
+
+            if (SDL2Driver.MainThreadDispatcher != null)
+            {
+                SDL2Driver.MainThreadDispatcher(createSurface);
+            }
+            else
+            {
+                createSurface();
             }
 
             return (IntPtr)surfaceHandle;

+ 26 - 8
Ryujinx.Headless.SDL2/WindowBase.cs

@@ -11,6 +11,7 @@ using Ryujinx.Input;
 using Ryujinx.Input.HLE;
 using Ryujinx.SDL2.Common;
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Threading;
@@ -26,6 +27,13 @@ namespace Ryujinx.Headless.SDL2
         private const 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 const int TargetFps = 60;
 
+        private static ConcurrentQueue<Action> MainThreadActions = new ConcurrentQueue<Action>();
+
+        public static void QueueMainThreadAction(Action action)
+        {
+            MainThreadActions.Enqueue(action);
+        }
+
         public NpadManager NpadManager { get; }
         public TouchScreenManager TouchScreenManager { get; }
         public Switch Device { get; private set; }
@@ -168,6 +176,14 @@ namespace Ryujinx.Headless.SDL2
 
         public void Render()
         {
+            InitializeWindowRenderer();
+
+            Device.Gpu.Renderer.Initialize(_glLogLevel);
+
+            InitializeRenderer();
+
+            _gpuVendorName = GetGpuVendorName();
+
             Device.Gpu.Renderer.RunLoop(() =>
             {
                 Device.Gpu.SetGpuThread();
@@ -241,6 +257,14 @@ namespace Ryujinx.Headless.SDL2
             _exitEvent.Dispose();
         }
 
+        public void ProcessMainThreadQueue()
+        {
+            while (MainThreadActions.TryDequeue(out Action action))
+            {
+                action();
+            }
+        }
+
         public void MainLoop()
         {
             while (_isActive)
@@ -249,6 +273,8 @@ namespace Ryujinx.Headless.SDL2
 
                 SDL_PumpEvents();
 
+                ProcessMainThreadQueue();
+
                 // Polling becomes expensive if it's not slept
                 Thread.Sleep(1);
             }
@@ -315,14 +341,6 @@ namespace Ryujinx.Headless.SDL2
 
             InitializeWindow();
 
-            InitializeWindowRenderer();
-
-            Device.Gpu.Renderer.Initialize(_glLogLevel);
-
-            InitializeRenderer();
-
-            _gpuVendorName = GetGpuVendorName();
-
             Thread renderLoopThread = new Thread(Render)
             {
                 Name = "GUI.RenderLoop"

+ 2 - 1
Ryujinx.Memory/MemoryManagerUnixHelper.cs

@@ -153,7 +153,8 @@ namespace Ryujinx.Memory
 
             if (OperatingSystem.IsMacOSVersionAtLeast(10, 14))
             {
-                result |= MAP_JIT_DARWIN;
+                // Only to be used with the Hardened Runtime.
+                // result |= MAP_JIT_DARWIN;
             }
 
             return result;

+ 1 - 1
Ryujinx/Modules/Updater/UpdateDialog.cs

@@ -25,7 +25,7 @@ namespace Ryujinx.Modules
 
         public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { }
 
-        private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetObject("UpdateDialog").Handle)
+        private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetRawOwnedObject("UpdateDialog"))
         {
             builder.Autoconnect(this);
 

+ 36 - 0
Ryujinx/Program.cs

@@ -16,6 +16,7 @@ using Ryujinx.Ui.Widgets;
 using SixLabors.ImageSharp.Formats.Jpeg;
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
 using System.Runtime.InteropServices;
 using System.Threading.Tasks;
@@ -40,6 +41,12 @@ namespace Ryujinx
         [DllImport("user32.dll", SetLastError = true)]
         public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
 
+        [DllImport("libc", SetLastError = true)]
+        static extern int setenv(string name, string value, int overwrite);
+
+        [DllImport("libc")]
+        static extern IntPtr getenv(string name);
+
         private const uint MB_ICONWARNING = 0x30;
 
         static Program()
@@ -97,6 +104,35 @@ namespace Ryujinx
                 XInitThreads();
             }
 
+            if (OperatingSystem.IsMacOS())
+            {
+                string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
+                string resourcesDataDir;
+
+                if (Path.GetFileName(baseDirectory) == "MacOS")
+                {
+                    resourcesDataDir = Path.Combine(Directory.GetParent(baseDirectory).FullName, "Resources");
+                }
+                else
+                {
+                    resourcesDataDir = baseDirectory;
+                }
+
+                void SetEnvironmentVariableNoCaching(string key, string value)
+                {
+                    int res = setenv(key, value, 1);
+                    Debug.Assert(res != -1);
+                }
+
+                // On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories.
+                SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share"));
+
+                // On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories.
+                SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache"));
+
+                SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache"));
+            }
+
             string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
             Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}");
 

+ 7 - 8
Ryujinx/Ryujinx.csproj

@@ -19,10 +19,13 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="GtkSharp" Version="3.22.25.128" />
-    <PackageReference Include="GtkSharp.Dependencies" Version="1.1.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
-    <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
-    <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
+    <PackageReference Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
+    <PackageReference Include="GtkSharp.Dependencies" Version="1.1.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
+    <PackageReference Include="GtkSharp.Dependencies.osx" Version="0.0.5" Condition="'$(RuntimeIdentifier)' == 'osx-x64' OR '$(RuntimeIdentifier)' == 'osx-arm64'" />
+    <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
+    <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.osx" Version="5.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
+    <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
+    <PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
     <PackageReference Include="OpenTK.Core" Version="4.7.5" />
     <PackageReference Include="OpenTK.Graphics" Version="4.7.5" />
     <PackageReference Include="SPB" Version="0.0.4-build28" />
@@ -62,10 +65,6 @@
     <ApplicationIcon>Ryujinx.ico</ApplicationIcon>
   </PropertyGroup>
 
-  <PropertyGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64'">
-    <DefineConstants>$(DefineConstants);MACOS_BUILD</DefineConstants>
-  </PropertyGroup>
-
   <ItemGroup>
     <None Remove="Ui\MainWindow.glade" />
     <None Remove="Ui\Widgets\ProfileDialog.glade" />

+ 134 - 0
Ryujinx/Ui/Helper/MetalHelper.cs

@@ -0,0 +1,134 @@
+using Gdk;
+using System;
+using System.Runtime.Versioning;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Ui.Helper
+{
+    public delegate void UpdateBoundsCallbackDelegate(Window window);
+
+    [SupportedOSPlatform("macos")]
+    static class MetalHelper
+    {
+        private const string LibObjCImport = "/usr/lib/libobjc.A.dylib";
+
+        private struct Selector
+        {
+            public readonly IntPtr NativePtr;
+
+            public unsafe Selector(string value)
+            {
+                int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
+                byte* data = stackalloc byte[size];
+
+                fixed (char* pValue = value)
+                {
+                    System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
+                }
+
+                NativePtr = sel_registerName(data);
+            }
+
+            public static implicit operator Selector(string value) => new Selector(value);
+        }
+
+        private static unsafe IntPtr GetClass(string value)
+        {
+            int size = System.Text.Encoding.UTF8.GetMaxByteCount(value.Length);
+            byte* data = stackalloc byte[size];
+
+            fixed (char* pValue = value)
+            {
+                System.Text.Encoding.UTF8.GetBytes(pValue, value.Length, data, size);
+            }
+
+            return objc_getClass(data);
+        }
+
+        private struct NSPoint
+        {
+            public double X;
+            public double Y;
+
+            public NSPoint(double x, double y)
+            {
+                X = x;
+                Y = y;
+            }
+        }
+
+        private struct NSRect
+        {
+            public NSPoint Pos;
+            public NSPoint Size;
+
+            public NSRect(double x, double y, double width, double height)
+            {
+                Pos = new NSPoint(x, y);
+                Size = new NSPoint(width, height);
+            }
+        }
+
+        public static IntPtr GetMetalLayer(Display display, Window window, out IntPtr nsView, out UpdateBoundsCallbackDelegate updateBounds)
+        {
+            nsView = gdk_quartz_window_get_nsview(window.Handle);
+
+            // Create a new CAMetalLayer.
+            IntPtr layerClass = GetClass("CAMetalLayer");
+            IntPtr metalLayer = IntPtr_objc_msgSend(layerClass, "alloc");
+            objc_msgSend(metalLayer, "init");
+
+            // Create a child NSView to render into.
+            IntPtr nsViewClass = GetClass("NSView");
+            IntPtr child = IntPtr_objc_msgSend(nsViewClass, "alloc");
+            objc_msgSend(child, "init", new NSRect());
+
+            // Add it as a child.
+            objc_msgSend(nsView, "addSubview:", child);
+
+            // Make its renderer our metal layer.
+            objc_msgSend(child, "setWantsLayer:", (byte)1);
+            objc_msgSend(child, "setLayer:", metalLayer);
+            objc_msgSend(metalLayer, "setContentsScale:", (double)display.GetMonitorAtWindow(window).ScaleFactor);
+
+            // Set the frame position/location.
+            updateBounds = (Window window) => {
+                window.GetPosition(out int x, out int y);
+                int width = window.Width;
+                int height = window.Height;
+                objc_msgSend(child, "setFrame:", new NSRect(x, y, width, height));
+            };
+
+            updateBounds(window);
+
+            return metalLayer;
+        }
+
+        [DllImport(LibObjCImport)]
+        private static unsafe extern IntPtr sel_registerName(byte* data);
+
+        [DllImport(LibObjCImport)]
+        private static unsafe extern IntPtr objc_getClass(byte* data);
+
+        [DllImport(LibObjCImport)]
+        private static extern void objc_msgSend(IntPtr receiver, Selector selector);
+
+        [DllImport(LibObjCImport)]
+        private static extern void objc_msgSend(IntPtr receiver, Selector selector, byte value);
+
+        [DllImport(LibObjCImport)]
+        private static extern void objc_msgSend(IntPtr receiver, Selector selector, IntPtr value);
+
+        [DllImport(LibObjCImport)]
+        private static extern void objc_msgSend(IntPtr receiver, Selector selector, NSRect point);
+
+        [DllImport(LibObjCImport)]
+        private static extern void objc_msgSend(IntPtr receiver, Selector selector, double value);
+
+        [DllImport(LibObjCImport, EntryPoint = "objc_msgSend")]
+        private static extern IntPtr IntPtr_objc_msgSend(IntPtr receiver, Selector selector);
+
+        [DllImport("libgdk-3.0.dylib")]
+        private static extern IntPtr gdk_quartz_window_get_nsview(IntPtr gdkWindow);
+    }
+}

+ 2 - 5
Ryujinx/Ui/MainWindow.cs

@@ -142,7 +142,7 @@ namespace Ryujinx.Ui
 
         public MainWindow() : this(new Builder("Ryujinx.Ui.MainWindow.glade")) { }
 
-        private MainWindow(Builder builder) : base(builder.GetObject("_mainWin").Handle)
+        private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin"))
         {
             builder.Autoconnect(this);
 
@@ -846,9 +846,7 @@ namespace Ryujinx.Ui
                 _deviceExitStatus.Reset();
 
                 Translator.IsReadyForTranslation.Reset();
-#if MACOS_BUILD
-                CreateGameWindow();
-#else
+
                 Thread windowThread = new Thread(() =>
                 {
                     CreateGameWindow();
@@ -858,7 +856,6 @@ namespace Ryujinx.Ui
                 };
 
                 windowThread.Start();
-#endif
 
                 _gameLoaded           = true;
                 _actionMenu.Sensitive = true;

+ 14 - 1
Ryujinx/Ui/VKRenderer.cs

@@ -1,9 +1,11 @@
 using Gdk;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Input.HLE;
+using Ryujinx.Ui.Helper;
 using SPB.Graphics.Vulkan;
 using SPB.Platform.Win32;
 using SPB.Platform.X11;
+using SPB.Platform.Metal;
 using SPB.Windowing;
 using System;
 using System.Runtime.InteropServices;
@@ -13,6 +15,7 @@ namespace Ryujinx.Ui
     public class VKRenderer : RendererWidgetBase
     {
         public NativeWindowBase NativeWindow { get; private set; }
+        private UpdateBoundsCallbackDelegate _updateBoundsCallback;
 
         public VKRenderer(InputManager inputManager, GraphicsDebugLevel glLogLevel) : base(inputManager, glLogLevel) { }
 
@@ -31,6 +34,12 @@ namespace Ryujinx.Ui
 
                 return new SimpleX11Window(new NativeHandle(displayHandle), new NativeHandle(windowHandle));
             }
+            else if (OperatingSystem.IsMacOS())
+            {
+                IntPtr metalLayer = MetalHelper.GetMetalLayer(Display, Window, out IntPtr nsView, out _updateBoundsCallback);
+
+                return new SimpleMetalWindow(new NativeHandle(nsView), new NativeHandle(metalLayer));
+            }
 
             throw new NotImplementedException();
         }
@@ -53,7 +62,11 @@ namespace Ryujinx.Ui
                 WaitEvent.Set();
             }
 
-            return base.OnConfigureEvent(evnt);
+            bool result = base.OnConfigureEvent(evnt);
+
+            _updateBoundsCallback?.Invoke(Window);
+
+            return result;
         }
 
         public unsafe IntPtr CreateWindowSurface(IntPtr instance)

+ 1 - 1
Ryujinx/Ui/Widgets/ProfileDialog.cs

@@ -18,7 +18,7 @@ namespace Ryujinx.Ui.Widgets
 
         public ProfileDialog() : this(new Builder("Ryujinx.Ui.Widgets.ProfileDialog.glade")) { }
 
-        private ProfileDialog(Builder builder) : base(builder.GetObject("_profileDialog").Handle)
+        private ProfileDialog(Builder builder) : base(builder.GetRawOwnedObject("_profileDialog"))
         {
             builder.Autoconnect(this);
             Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");

+ 1 - 1
Ryujinx/Ui/Windows/CheatWindow.cs

@@ -23,7 +23,7 @@ namespace Ryujinx.Ui.Windows
 
         public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName) { }
 
-        private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) : base(builder.GetObject("_cheatWindow").Handle)
+        private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) : base(builder.GetRawOwnedObject("_cheatWindow"))
         {
             builder.Autoconnect(this);
             _baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";

+ 10 - 7
Ryujinx/Ui/Windows/ControllerWindow.cs

@@ -119,7 +119,7 @@ namespace Ryujinx.Ui.Windows
 
         public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Ui.Windows.ControllerWindow.glade"), controllerId) { }
 
-        private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetObject("_controllerWin").Handle)
+        private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin"))
         {
             _mainWindow = mainWindow;
             _selectedGamepad = null;
@@ -379,13 +379,16 @@ namespace Ryujinx.Ui.Windows
                     break;
             }
 
-            _controllerImage.Pixbuf = _controllerType.ActiveId switch
+            if (!OperatingSystem.IsMacOS())
             {
-                "ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_ProCon.svg", 400, 400),
-                "JoyconLeft"    => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConLeft.svg", 400, 500),
-                "JoyconRight"   => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConRight.svg", 400, 500),
-                _               => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConPair.svg", 400, 500),
-            };
+                _controllerImage.Pixbuf = _controllerType.ActiveId switch
+                {
+                    "ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_ProCon.svg", 400, 400),
+                    "JoyconLeft"    => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConLeft.svg", 400, 500),
+                    "JoyconRight"   => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConRight.svg", 400, 500),
+                    _               => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConPair.svg", 400, 500),
+                };
+            }
         }
 
         private void ClearValues()

+ 1 - 1
Ryujinx/Ui/Windows/DlcWindow.cs

@@ -34,7 +34,7 @@ namespace Ryujinx.Ui.Windows
 
         public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
 
-        private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_dlcWindow").Handle)
+        private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow"))
         {
             builder.Autoconnect(this);
 

+ 11 - 2
Ryujinx/Ui/Windows/SettingsWindow.cs

@@ -113,7 +113,7 @@ namespace Ryujinx.Ui.Windows
 
         public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Ui.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
 
-        private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetObject("_settingsWin").Handle)
+        private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetRawOwnedObject("_settingsWin"))
         {
             Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
 
@@ -422,7 +422,7 @@ namespace Ryujinx.Ui.Windows
             Task.Run(() =>
             {
                 openAlIsSupported  = OpenALHardwareDeviceDriver.IsSupported;
-                soundIoIsSupported = SoundIoHardwareDeviceDriver.IsSupported;
+                soundIoIsSupported = !OperatingSystem.IsMacOS() && SoundIoHardwareDeviceDriver.IsSupported;
                 sdl2IsSupported    = SDL2HardwareDeviceDriver.IsSupported;
             });
 
@@ -438,6 +438,15 @@ namespace Ryujinx.Ui.Windows
                     _ => throw new ArgumentOutOfRangeException()
                 };
             });
+
+            if (OperatingSystem.IsMacOS())
+            {
+                var store = (_graphicsBackend.Model as ListStore);
+                store.GetIter(out TreeIter openglIter, new TreePath(new int[] {1}));
+                store.Remove(ref openglIter);
+
+                _graphicsBackend.Model = store;
+            }
         }
 
         private void UpdatePreferredGpuComboBox()

+ 1 - 1
Ryujinx/Ui/Windows/TitleUpdateWindow.cs

@@ -40,7 +40,7 @@ namespace Ryujinx.Ui.Windows
 
         public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
 
-        private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetObject("_titleUpdateWindow").Handle)
+        private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
         {
             _parent = parent;