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 000000000..0e8da15e6 Binary files /dev/null and b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png differ 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;