diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs
index a016ebd5..0cb3bd13 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 00000000..ae07ce69
--- /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 23b9ef8e..d929331d 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 6d963a40..24bdf22d 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 7acbefca..6ef15982 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 236a0a16..b9c5f75f 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 bd4a55e8..c752697b 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 a9da8d7d..33c35c7e 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 c8c9f59a..bd3dd613 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 1d217f58..42b76453 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 7813bb81..942970c2 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 00000000..0e8da15e
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 6c0955ec..8216a65e 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 82f3483c..ec5d2680 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 bfc33edc..50a90763 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 15286ea3..83ae87eb 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 6eacadc1..18323339 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 9aa17936..88b0d573 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 8e6e7935..dd31c328 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 cb71fafc..a1556713 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 162bd89d..3baddca3 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 31f130c4..ba50c109 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 00000000..62ca2930
--- /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 5216c774..3a5b7723 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 7e02c689..63d0d0a6 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 8748737c..96b44d24 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 a9dccd34..917603b2 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 d043d023..002f8fe2 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 1a47ae41..0a97ac2a 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 90197318..220bb82a 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 94bf9e70..2618168c 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;