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

* Initial implementation of metal surface across UIs

* Fix SDL2 on windows

* Update Ryujinx/Ryujinx.csproj

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

* Address Feedback

Co-authored-by: Mary-nyan <thog@protonmail.com>
This commit is contained in:
riperiperi 2022-12-06 22:00:25 +00:00 committed by GitHub
parent d3709a753f
commit e211c3f00a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 495 additions and 63 deletions

View file

@ -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);

View file

@ -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}");

View file

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

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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)

View file

@ -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");

View file

@ -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}]";

View file

@ -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()

View file

@ -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);

View file

@ -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()

View file

@ -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;