From e211c3f00a847f50b286349918e5c51967862e93 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Tue, 6 Dec 2022 22:00:25 +0000 Subject: [PATCH] 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 * Address Feedback Co-authored-by: Mary-nyan --- Ryujinx.Ava/AppHost.cs | 2 +- Ryujinx.Ava/Helper/MetalHelper.cs | 127 +++++++++++++++++ Ryujinx.Ava/Program.cs | 9 +- Ryujinx.Ava/Ryujinx.Ava.csproj | 6 +- Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs | 33 ++++- .../Ui/Controls/VulkanEmbeddedWindow.cs | 5 + .../Ui/ViewModels/SettingsViewModel.cs | 2 + Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs | 6 + Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml | 2 +- .../Configuration/AppDataManager.cs | 9 +- .../VulkanInitialization.cs | 12 +- .../Resources/Logo_Ryujinx.png | Bin 0 -> 52972 bytes .../SoftwareKeyboardRendererBase.cs | 8 +- Ryujinx.HLE/Ryujinx.HLE.csproj | 2 + Ryujinx.Headless.SDL2/Program.cs | 20 +++ .../Ryujinx.Headless.SDL2.csproj | 3 +- Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs | 28 +++- Ryujinx.Headless.SDL2/WindowBase.cs | 34 +++-- Ryujinx.Memory/MemoryManagerUnixHelper.cs | 3 +- Ryujinx/Modules/Updater/UpdateDialog.cs | 2 +- Ryujinx/Program.cs | 36 +++++ Ryujinx/Ryujinx.csproj | 15 +- Ryujinx/Ui/Helper/MetalHelper.cs | 134 ++++++++++++++++++ Ryujinx/Ui/MainWindow.cs | 7 +- Ryujinx/Ui/VKRenderer.cs | 15 +- Ryujinx/Ui/Widgets/ProfileDialog.cs | 2 +- Ryujinx/Ui/Windows/CheatWindow.cs | 2 +- Ryujinx/Ui/Windows/ControllerWindow.cs | 17 ++- Ryujinx/Ui/Windows/DlcWindow.cs | 2 +- Ryujinx/Ui/Windows/SettingsWindow.cs | 13 +- Ryujinx/Ui/Windows/TitleUpdateWindow.cs | 2 +- 31 files changed, 495 insertions(+), 63 deletions(-) create mode 100644 Ryujinx.Ava/Helper/MetalHelper.cs create mode 100644 Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png create mode 100644 Ryujinx/Ui/Helper/MetalHelper.cs diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs index a016ebd5a..0cb3bd138 100644 --- a/Ryujinx.Ava/AppHost.cs +++ b/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; diff --git a/Ryujinx.Ava/Helper/MetalHelper.cs b/Ryujinx.Ava/Helper/MetalHelper.cs new file mode 100644 index 000000000..ae07ce69b --- /dev/null +++ b/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); + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/Program.cs b/Ryujinx.Ava/Program.cs index 23b9ef8ea..d929331db 100644 --- a/Ryujinx.Ava/Program.cs +++ b/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); diff --git a/Ryujinx.Ava/Ryujinx.Ava.csproj b/Ryujinx.Ava/Ryujinx.Ava.csproj index 6d963a403..24bdf22de 100644 --- a/Ryujinx.Ava/Ryujinx.Ava.csproj +++ b/Ryujinx.Ava/Ryujinx.Ava.csproj @@ -31,8 +31,10 @@ - - + + + + diff --git a/Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs b/Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs index 7acbefca5..6ef159821 100644 --- a/Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs +++ b/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 WindowCreated; public event EventHandler 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); + } } } \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs b/Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs index 236a0a166..b9c5f75f5 100644 --- a/Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs +++ b/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(); diff --git a/Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs b/Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs index bd4a55e8f..c752697b9 100644 --- a/Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs +++ b/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; diff --git a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs index a9da8d7d0..33c35c7e0 100644 --- a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs +++ b/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) diff --git a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml index c8c9f59a8..bd3dd613e 100644 --- a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml +++ b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml @@ -540,7 +540,7 @@ - + diff --git a/Ryujinx.Common/Configuration/AppDataManager.cs b/Ryujinx.Common/Configuration/AppDataManager.cs index 1d217f587..42b76453b 100644 --- a/Ryujinx.Common/Configuration/AppDataManager.cs +++ b/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)) diff --git a/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 7813bb816..942970c27 100644 --- a/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/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, diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png new file mode 100644 index 0000000000000000000000000000000000000000..0e8da15e6ee20873f2ad7924f77d2952c20c4b14 GIT binary patch literal 52972 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 Vgi5w;Wnf@n@O1TaS?83{1OSf*k@x@r literal 0 HcmV?d00001 diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs index 6c0955ecc..8216a65ee 100644 --- a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs +++ b/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) diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index 82f3483cd..ec5d26807 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -36,6 +36,7 @@ + @@ -44,6 +45,7 @@ + diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs index bfc33edcf..50a90763f 100644 --- a/Ryujinx.Headless.SDL2/Program.cs +++ b/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; diff --git a/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj index 15286ea3a..83ae87eb8 100644 --- a/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj +++ b/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj @@ -12,7 +12,8 @@ - + + diff --git a/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs b/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs index 6eacadc15..183233397 100644 --- a/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs +++ b/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; diff --git a/Ryujinx.Headless.SDL2/WindowBase.cs b/Ryujinx.Headless.SDL2/WindowBase.cs index 9aa17936f..88b0d5733 100644 --- a/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/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 MainThreadActions = new ConcurrentQueue(); + + 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" diff --git a/Ryujinx.Memory/MemoryManagerUnixHelper.cs b/Ryujinx.Memory/MemoryManagerUnixHelper.cs index 8e6e79352..dd31c328b 100644 --- a/Ryujinx.Memory/MemoryManagerUnixHelper.cs +++ b/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; diff --git a/Ryujinx/Modules/Updater/UpdateDialog.cs b/Ryujinx/Modules/Updater/UpdateDialog.cs index cb71fafc9..a1556713a 100644 --- a/Ryujinx/Modules/Updater/UpdateDialog.cs +++ b/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); diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 162bd89d5..3baddca3f 100644 --- a/Ryujinx/Program.cs +++ b/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}"); diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index 31f130c4a..ba50c109d 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -19,10 +19,13 @@ - - - - + + + + + + + @@ -62,10 +65,6 @@ Ryujinx.ico - - $(DefineConstants);MACOS_BUILD - - diff --git a/Ryujinx/Ui/Helper/MetalHelper.cs b/Ryujinx/Ui/Helper/MetalHelper.cs new file mode 100644 index 000000000..62ca29301 --- /dev/null +++ b/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); + } +} \ No newline at end of file diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 5216c7747..3a5b7723d 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/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; diff --git a/Ryujinx/Ui/VKRenderer.cs b/Ryujinx/Ui/VKRenderer.cs index 7e02c689d..63d0d0a62 100644 --- a/Ryujinx/Ui/VKRenderer.cs +++ b/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) diff --git a/Ryujinx/Ui/Widgets/ProfileDialog.cs b/Ryujinx/Ui/Widgets/ProfileDialog.cs index 8748737c7..96b44d240 100644 --- a/Ryujinx/Ui/Widgets/ProfileDialog.cs +++ b/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"); diff --git a/Ryujinx/Ui/Windows/CheatWindow.cs b/Ryujinx/Ui/Windows/CheatWindow.cs index a9dccd34f..917603b29 100644 --- a/Ryujinx/Ui/Windows/CheatWindow.cs +++ b/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}]"; diff --git a/Ryujinx/Ui/Windows/ControllerWindow.cs b/Ryujinx/Ui/Windows/ControllerWindow.cs index d043d0238..002f8fe22 100644 --- a/Ryujinx/Ui/Windows/ControllerWindow.cs +++ b/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() diff --git a/Ryujinx/Ui/Windows/DlcWindow.cs b/Ryujinx/Ui/Windows/DlcWindow.cs index 1a47ae414..0a97ac2a2 100644 --- a/Ryujinx/Ui/Windows/DlcWindow.cs +++ b/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); diff --git a/Ryujinx/Ui/Windows/SettingsWindow.cs b/Ryujinx/Ui/Windows/SettingsWindow.cs index 901973188..220bb82ae 100644 --- a/Ryujinx/Ui/Windows/SettingsWindow.cs +++ b/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() diff --git a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs b/Ryujinx/Ui/Windows/TitleUpdateWindow.cs index 94bf9e709..2618168cd 100644 --- a/Ryujinx/Ui/Windows/TitleUpdateWindow.cs +++ b/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;