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:
parent
d3709a753f
commit
e211c3f00a
31 changed files with 495 additions and 63 deletions
|
@ -125,7 +125,7 @@ namespace Ryujinx.Ava
|
||||||
_inputManager = inputManager;
|
_inputManager = inputManager;
|
||||||
_accountManager = accountManager;
|
_accountManager = accountManager;
|
||||||
_userChannelPersistence = userChannelPersistence;
|
_userChannelPersistence = userChannelPersistence;
|
||||||
_renderingThread = new Thread(RenderLoop) { Name = "GUI.RenderThread" };
|
_renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" };
|
||||||
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
|
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
||||||
|
|
127
Ryujinx.Ava/Helper/MetalHelper.cs
Normal file
127
Ryujinx.Ava/Helper/MetalHelper.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ namespace Ryujinx.Ava
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
public static double WindowScaleFactor { get; 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 Version { get; private set; }
|
||||||
public static string ConfigurationPath { get; private set; }
|
public static string ConfigurationPath { get; private set; }
|
||||||
public static bool PreviewerDetached { get; private set; }
|
public static bool PreviewerDetached { get; private set; }
|
||||||
|
|
|
@ -31,8 +31,10 @@
|
||||||
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||||
|
|
||||||
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
|
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
|
||||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||||
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.osx" Version="5.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
|
||||||
|
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
|
<PackageReference Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||||
|
|
|
@ -2,11 +2,10 @@ using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
|
using Ryujinx.Ava.Ui.Helper;
|
||||||
using SPB.Graphics;
|
using SPB.Graphics;
|
||||||
using SPB.Platform;
|
using SPB.Platform;
|
||||||
using SPB.Platform.GLX;
|
using SPB.Platform.GLX;
|
||||||
using SPB.Platform.X11;
|
|
||||||
using SPB.Windowing;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
@ -23,6 +22,10 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
protected GLXWindow X11Window { get; set; }
|
protected GLXWindow X11Window { get; set; }
|
||||||
protected IntPtr WindowHandle { get; set; }
|
protected IntPtr WindowHandle { get; set; }
|
||||||
protected IntPtr X11Display { get; set; }
|
protected IntPtr X11Display { get; set; }
|
||||||
|
protected IntPtr NsView { get; set; }
|
||||||
|
protected IntPtr MetalLayer { get; set; }
|
||||||
|
|
||||||
|
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||||
|
|
||||||
public event EventHandler<IntPtr> WindowCreated;
|
public event EventHandler<IntPtr> WindowCreated;
|
||||||
public event EventHandler<Size> SizeChanged;
|
public event EventHandler<Size> SizeChanged;
|
||||||
|
@ -58,6 +61,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
private void StateChanged(Rect rect)
|
private void StateChanged(Rect rect)
|
||||||
{
|
{
|
||||||
SizeChanged?.Invoke(this, rect.Size);
|
SizeChanged?.Invoke(this, rect.Size);
|
||||||
|
_updateBoundsCallback?.Invoke(rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
||||||
|
@ -70,6 +74,11 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
{
|
{
|
||||||
return CreateWin32(parent);
|
return CreateWin32(parent);
|
||||||
}
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
return CreateMacOs(parent);
|
||||||
|
}
|
||||||
|
|
||||||
return base.CreateNativeControlCore(parent);
|
return base.CreateNativeControlCore(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +94,10 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
{
|
{
|
||||||
DestroyWin32(control);
|
DestroyWin32(control);
|
||||||
}
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
DestroyMacOS();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
base.DestroyNativeControlCore(control);
|
base.DestroyNativeControlCore(control);
|
||||||
|
@ -187,6 +200,16 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
return DefWindowProc(hWnd, msg, (IntPtr)wParam, (IntPtr)lParam);
|
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()
|
void DestroyLinux()
|
||||||
{
|
{
|
||||||
X11Window?.Dispose();
|
X11Window?.Dispose();
|
||||||
|
@ -198,5 +221,11 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
DestroyWindow(handle.Handle);
|
DestroyWindow(handle.Handle);
|
||||||
UnregisterClass(_className, GetModuleHandle(null));
|
UnregisterClass(_className, GetModuleHandle(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
void DestroyMacOS()
|
||||||
|
{
|
||||||
|
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ using Ryujinx.Ava.Ui.Controls;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using SPB.Graphics.Vulkan;
|
using SPB.Graphics.Vulkan;
|
||||||
using SPB.Platform.GLX;
|
using SPB.Platform.GLX;
|
||||||
|
using SPB.Platform.Metal;
|
||||||
using SPB.Platform.Win32;
|
using SPB.Platform.Win32;
|
||||||
using SPB.Platform.X11;
|
using SPB.Platform.X11;
|
||||||
using SPB.Windowing;
|
using SPB.Windowing;
|
||||||
|
@ -37,6 +38,10 @@ namespace Ryujinx.Ava.Ui
|
||||||
{
|
{
|
||||||
_window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
|
_window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
|
||||||
}
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
_window = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new PlatformNotSupportedException();
|
throw new PlatformNotSupportedException();
|
||||||
|
|
|
@ -108,6 +108,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
|
||||||
|
|
||||||
public bool DirectoryChanged
|
public bool DirectoryChanged
|
||||||
{
|
{
|
||||||
get => _directoryChanged;
|
get => _directoryChanged;
|
||||||
|
|
|
@ -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)
|
public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.Application != null)
|
if (args.Application != null)
|
||||||
|
|
|
@ -540,7 +540,7 @@
|
||||||
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
|
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
|
||||||
<TextBlock Text="Vulkan" />
|
<TextBlock Text="Vulkan" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
<ComboBoxItem>
|
<ComboBoxItem IsEnabled="{Binding IsOpenGLAvailable}">
|
||||||
<TextBlock Text="OpenGL" />
|
<TextBlock Text="OpenGL" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
|
|
|
@ -45,7 +45,14 @@ namespace Ryujinx.Common.Configuration
|
||||||
|
|
||||||
public static void Initialize(string baseDirPath)
|
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);
|
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
|
||||||
|
|
||||||
if (Directory.Exists(portablePath))
|
if (Directory.Exists(portablePath))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
|
@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
ExtConditionalRendering.ExtensionName,
|
ExtConditionalRendering.ExtensionName,
|
||||||
ExtExtendedDynamicState.ExtensionName,
|
ExtExtendedDynamicState.ExtensionName,
|
||||||
|
ExtTransformFeedback.ExtensionName,
|
||||||
KhrDrawIndirectCount.ExtensionName,
|
KhrDrawIndirectCount.ExtensionName,
|
||||||
KhrPushDescriptor.ExtensionName,
|
KhrPushDescriptor.ExtensionName,
|
||||||
"VK_EXT_custom_border_color",
|
"VK_EXT_custom_border_color",
|
||||||
|
@ -36,8 +37,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public static string[] RequiredExtensions { get; } = new string[]
|
public static string[] RequiredExtensions { get; } = new string[]
|
||||||
{
|
{
|
||||||
KhrSwapchain.ExtensionName,
|
KhrSwapchain.ExtensionName
|
||||||
ExtTransformFeedback.ExtensionName
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private static string[] _excludedMessages = new string[]
|
private static string[] _excludedMessages = new string[]
|
||||||
|
@ -382,12 +382,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
DepthClamp = true,
|
DepthClamp = true,
|
||||||
DualSrcBlend = true,
|
DualSrcBlend = true,
|
||||||
FragmentStoresAndAtomics = true,
|
FragmentStoresAndAtomics = true,
|
||||||
GeometryShader = true,
|
GeometryShader = supportedFeatures.GeometryShader,
|
||||||
ImageCubeArray = true,
|
ImageCubeArray = true,
|
||||||
IndependentBlend = true,
|
IndependentBlend = true,
|
||||||
LogicOp = true,
|
LogicOp = supportedFeatures.LogicOp,
|
||||||
MultiViewport = true,
|
MultiViewport = true,
|
||||||
PipelineStatisticsQuery = true,
|
PipelineStatisticsQuery = supportedFeatures.PipelineStatisticsQuery,
|
||||||
SamplerAnisotropy = true,
|
SamplerAnisotropy = true,
|
||||||
ShaderClipDistance = true,
|
ShaderClipDistance = true,
|
||||||
ShaderFloat64 = supportedFeatures.ShaderFloat64,
|
ShaderFloat64 = supportedFeatures.ShaderFloat64,
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
|
@ -11,7 +11,6 @@ using System.Numerics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using Ryujinx.Common;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
{
|
{
|
||||||
|
@ -68,8 +67,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
{
|
{
|
||||||
int ryujinxLogoSize = 32;
|
int ryujinxLogoSize = 32;
|
||||||
|
|
||||||
Stream logoStream = EmbeddedResources.GetStream("Ryujinx.Ui.Common/Resources/Logo_Ryujinx.png");
|
string ryujinxIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Logo_Ryujinx.png";
|
||||||
_ryujinxLogo = LoadResource(logoStream, ryujinxLogoSize, ryujinxLogoSize);
|
_ryujinxLogo = LoadResource(Assembly.GetExecutingAssembly(), ryujinxIconPath, ryujinxLogoSize, ryujinxLogoSize);
|
||||||
|
|
||||||
string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
|
string padAcceptIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnA.png";
|
||||||
string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
|
string padCancelIconPath = "Ryujinx.HLE.HOS.Applets.SoftwareKeyboard.Resources.Icon_BtnB.png";
|
||||||
|
@ -117,7 +116,8 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||||
uiThemeFontFamily,
|
uiThemeFontFamily,
|
||||||
"Liberation Sans",
|
"Liberation Sans",
|
||||||
"FreeSans",
|
"FreeSans",
|
||||||
"DejaVu Sans"
|
"DejaVu Sans",
|
||||||
|
"Lucida Grande"
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (string fontFamily in availableFonts)
|
foreach (string fontFamily in availableFonts)
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Homebrew.npdm" />
|
<None Remove="Homebrew.npdm" />
|
||||||
|
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
||||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
||||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
|
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
|
||||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />
|
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Homebrew.npdm" />
|
<EmbeddedResource Include="Homebrew.npdm" />
|
||||||
|
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
||||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
||||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
|
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnB.png" />
|
||||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />
|
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_KeyF6.png" />
|
||||||
|
|
|
@ -77,6 +77,26 @@ namespace Ryujinx.Headless.SDL2
|
||||||
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient);
|
_accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient);
|
||||||
_userChannelPersistence = new UserChannelPersistence();
|
_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());
|
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
|
||||||
|
|
||||||
GraphicsConfig.EnableShaderCache = true;
|
GraphicsConfig.EnableShaderCache = true;
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
|
<PackageReference Include="OpenTK.Core" Version="4.7.5" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||||
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies.osx" Version="5.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Input.HLE;
|
using Ryujinx.Input.HLE;
|
||||||
|
using Ryujinx.SDL2.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using static SDL2.SDL;
|
using static SDL2.SDL;
|
||||||
|
@ -26,9 +27,18 @@ namespace Ryujinx.Headless.SDL2.Vulkan
|
||||||
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
|
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BasicInvoke(Action action)
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe IntPtr CreateWindowSurface(IntPtr instance)
|
public unsafe IntPtr CreateWindowSurface(IntPtr instance)
|
||||||
{
|
{
|
||||||
if (SDL_Vulkan_CreateSurface(WindowHandle, instance, out ulong surfaceHandle) == SDL_bool.SDL_FALSE)
|
ulong surfaceHandle = 0;
|
||||||
|
|
||||||
|
Action createSurface = () =>
|
||||||
|
{
|
||||||
|
if (SDL_Vulkan_CreateSurface(WindowHandle, instance, out surfaceHandle) == SDL_bool.SDL_FALSE)
|
||||||
{
|
{
|
||||||
string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\"";
|
string errorMessage = $"SDL_Vulkan_CreateSurface failed with error \"{SDL_GetError()}\"";
|
||||||
|
|
||||||
|
@ -36,6 +46,16 @@ namespace Ryujinx.Headless.SDL2.Vulkan
|
||||||
|
|
||||||
throw new Exception(errorMessage);
|
throw new Exception(errorMessage);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (SDL2Driver.MainThreadDispatcher != null)
|
||||||
|
{
|
||||||
|
SDL2Driver.MainThreadDispatcher(createSurface);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
createSurface();
|
||||||
|
}
|
||||||
|
|
||||||
return (IntPtr)surfaceHandle;
|
return (IntPtr)surfaceHandle;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ using Ryujinx.Input;
|
||||||
using Ryujinx.Input.HLE;
|
using Ryujinx.Input.HLE;
|
||||||
using Ryujinx.SDL2.Common;
|
using Ryujinx.SDL2.Common;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading;
|
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 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 const int TargetFps = 60;
|
||||||
|
|
||||||
|
private static ConcurrentQueue<Action> MainThreadActions = new ConcurrentQueue<Action>();
|
||||||
|
|
||||||
|
public static void QueueMainThreadAction(Action action)
|
||||||
|
{
|
||||||
|
MainThreadActions.Enqueue(action);
|
||||||
|
}
|
||||||
|
|
||||||
public NpadManager NpadManager { get; }
|
public NpadManager NpadManager { get; }
|
||||||
public TouchScreenManager TouchScreenManager { get; }
|
public TouchScreenManager TouchScreenManager { get; }
|
||||||
public Switch Device { get; private set; }
|
public Switch Device { get; private set; }
|
||||||
|
@ -168,6 +176,14 @@ namespace Ryujinx.Headless.SDL2
|
||||||
|
|
||||||
public void Render()
|
public void Render()
|
||||||
{
|
{
|
||||||
|
InitializeWindowRenderer();
|
||||||
|
|
||||||
|
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||||
|
|
||||||
|
InitializeRenderer();
|
||||||
|
|
||||||
|
_gpuVendorName = GetGpuVendorName();
|
||||||
|
|
||||||
Device.Gpu.Renderer.RunLoop(() =>
|
Device.Gpu.Renderer.RunLoop(() =>
|
||||||
{
|
{
|
||||||
Device.Gpu.SetGpuThread();
|
Device.Gpu.SetGpuThread();
|
||||||
|
@ -241,6 +257,14 @@ namespace Ryujinx.Headless.SDL2
|
||||||
_exitEvent.Dispose();
|
_exitEvent.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ProcessMainThreadQueue()
|
||||||
|
{
|
||||||
|
while (MainThreadActions.TryDequeue(out Action action))
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void MainLoop()
|
public void MainLoop()
|
||||||
{
|
{
|
||||||
while (_isActive)
|
while (_isActive)
|
||||||
|
@ -249,6 +273,8 @@ namespace Ryujinx.Headless.SDL2
|
||||||
|
|
||||||
SDL_PumpEvents();
|
SDL_PumpEvents();
|
||||||
|
|
||||||
|
ProcessMainThreadQueue();
|
||||||
|
|
||||||
// Polling becomes expensive if it's not slept
|
// Polling becomes expensive if it's not slept
|
||||||
Thread.Sleep(1);
|
Thread.Sleep(1);
|
||||||
}
|
}
|
||||||
|
@ -315,14 +341,6 @@ namespace Ryujinx.Headless.SDL2
|
||||||
|
|
||||||
InitializeWindow();
|
InitializeWindow();
|
||||||
|
|
||||||
InitializeWindowRenderer();
|
|
||||||
|
|
||||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
|
||||||
|
|
||||||
InitializeRenderer();
|
|
||||||
|
|
||||||
_gpuVendorName = GetGpuVendorName();
|
|
||||||
|
|
||||||
Thread renderLoopThread = new Thread(Render)
|
Thread renderLoopThread = new Thread(Render)
|
||||||
{
|
{
|
||||||
Name = "GUI.RenderLoop"
|
Name = "GUI.RenderLoop"
|
||||||
|
|
|
@ -153,7 +153,8 @@ namespace Ryujinx.Memory
|
||||||
|
|
||||||
if (OperatingSystem.IsMacOSVersionAtLeast(10, 14))
|
if (OperatingSystem.IsMacOSVersionAtLeast(10, 14))
|
||||||
{
|
{
|
||||||
result |= MAP_JIT_DARWIN;
|
// Only to be used with the Hardened Runtime.
|
||||||
|
// result |= MAP_JIT_DARWIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -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) { }
|
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);
|
builder.Autoconnect(this);
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ using Ryujinx.Ui.Widgets;
|
||||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -40,6 +41,12 @@ namespace Ryujinx
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
|
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;
|
private const uint MB_ICONWARNING = 0x30;
|
||||||
|
|
||||||
static Program()
|
static Program()
|
||||||
|
@ -97,6 +104,35 @@ namespace Ryujinx
|
||||||
XInitThreads();
|
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);
|
string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
|
||||||
Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}");
|
Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}");
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,13 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="GtkSharp" Version="3.22.25.128" />
|
<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'" />
|
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="GtkSharp.Dependencies.osx" Version="0.0.5" Condition="'$(RuntimeIdentifier)' == 'osx-x64' OR '$(RuntimeIdentifier)' == 'osx-arm64'" />
|
||||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' 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.Core" Version="4.7.5" />
|
||||||
<PackageReference Include="OpenTK.Graphics" Version="4.7.5" />
|
<PackageReference Include="OpenTK.Graphics" Version="4.7.5" />
|
||||||
<PackageReference Include="SPB" Version="0.0.4-build28" />
|
<PackageReference Include="SPB" Version="0.0.4-build28" />
|
||||||
|
@ -62,10 +65,6 @@
|
||||||
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
|
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64'">
|
|
||||||
<DefineConstants>$(DefineConstants);MACOS_BUILD</DefineConstants>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Ui\MainWindow.glade" />
|
<None Remove="Ui\MainWindow.glade" />
|
||||||
<None Remove="Ui\Widgets\ProfileDialog.glade" />
|
<None Remove="Ui\Widgets\ProfileDialog.glade" />
|
||||||
|
|
134
Ryujinx/Ui/Helper/MetalHelper.cs
Normal file
134
Ryujinx/Ui/Helper/MetalHelper.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -142,7 +142,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
public MainWindow() : this(new Builder("Ryujinx.Ui.MainWindow.glade")) { }
|
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);
|
builder.Autoconnect(this);
|
||||||
|
|
||||||
|
@ -846,9 +846,7 @@ namespace Ryujinx.Ui
|
||||||
_deviceExitStatus.Reset();
|
_deviceExitStatus.Reset();
|
||||||
|
|
||||||
Translator.IsReadyForTranslation.Reset();
|
Translator.IsReadyForTranslation.Reset();
|
||||||
#if MACOS_BUILD
|
|
||||||
CreateGameWindow();
|
|
||||||
#else
|
|
||||||
Thread windowThread = new Thread(() =>
|
Thread windowThread = new Thread(() =>
|
||||||
{
|
{
|
||||||
CreateGameWindow();
|
CreateGameWindow();
|
||||||
|
@ -858,7 +856,6 @@ namespace Ryujinx.Ui
|
||||||
};
|
};
|
||||||
|
|
||||||
windowThread.Start();
|
windowThread.Start();
|
||||||
#endif
|
|
||||||
|
|
||||||
_gameLoaded = true;
|
_gameLoaded = true;
|
||||||
_actionMenu.Sensitive = true;
|
_actionMenu.Sensitive = true;
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
using Gdk;
|
using Gdk;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Input.HLE;
|
using Ryujinx.Input.HLE;
|
||||||
|
using Ryujinx.Ui.Helper;
|
||||||
using SPB.Graphics.Vulkan;
|
using SPB.Graphics.Vulkan;
|
||||||
using SPB.Platform.Win32;
|
using SPB.Platform.Win32;
|
||||||
using SPB.Platform.X11;
|
using SPB.Platform.X11;
|
||||||
|
using SPB.Platform.Metal;
|
||||||
using SPB.Windowing;
|
using SPB.Windowing;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
@ -13,6 +15,7 @@ namespace Ryujinx.Ui
|
||||||
public class VKRenderer : RendererWidgetBase
|
public class VKRenderer : RendererWidgetBase
|
||||||
{
|
{
|
||||||
public NativeWindowBase NativeWindow { get; private set; }
|
public NativeWindowBase NativeWindow { get; private set; }
|
||||||
|
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||||
|
|
||||||
public VKRenderer(InputManager inputManager, GraphicsDebugLevel glLogLevel) : base(inputManager, glLogLevel) { }
|
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));
|
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();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
@ -53,7 +62,11 @@ namespace Ryujinx.Ui
|
||||||
WaitEvent.Set();
|
WaitEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.OnConfigureEvent(evnt);
|
bool result = base.OnConfigureEvent(evnt);
|
||||||
|
|
||||||
|
_updateBoundsCallback?.Invoke(Window);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe IntPtr CreateWindowSurface(IntPtr instance)
|
public unsafe IntPtr CreateWindowSurface(IntPtr instance)
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace Ryujinx.Ui.Widgets
|
||||||
|
|
||||||
public ProfileDialog() : this(new Builder("Ryujinx.Ui.Widgets.ProfileDialog.glade")) { }
|
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);
|
builder.Autoconnect(this);
|
||||||
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
|
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png");
|
||||||
|
|
|
@ -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) { }
|
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);
|
builder.Autoconnect(this);
|
||||||
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
|
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
|
||||||
|
|
|
@ -119,7 +119,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
|
|
||||||
public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Ui.Windows.ControllerWindow.glade"), controllerId) { }
|
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;
|
_mainWindow = mainWindow;
|
||||||
_selectedGamepad = null;
|
_selectedGamepad = null;
|
||||||
|
@ -379,6 +379,8 @@ namespace Ryujinx.Ui.Windows
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
_controllerImage.Pixbuf = _controllerType.ActiveId switch
|
_controllerImage.Pixbuf = _controllerType.ActiveId switch
|
||||||
{
|
{
|
||||||
"ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_ProCon.svg", 400, 400),
|
"ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_ProCon.svg", 400, 400),
|
||||||
|
@ -387,6 +389,7 @@ namespace Ryujinx.Ui.Windows
|
||||||
_ => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConPair.svg", 400, 500),
|
_ => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Ui.Common.Resources.Controller_JoyConPair.svg", 400, 500),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ClearValues()
|
private void ClearValues()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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) { }
|
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);
|
builder.Autoconnect(this);
|
||||||
|
|
||||||
|
|
|
@ -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) { }
|
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");
|
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(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
openAlIsSupported = OpenALHardwareDeviceDriver.IsSupported;
|
openAlIsSupported = OpenALHardwareDeviceDriver.IsSupported;
|
||||||
soundIoIsSupported = SoundIoHardwareDeviceDriver.IsSupported;
|
soundIoIsSupported = !OperatingSystem.IsMacOS() && SoundIoHardwareDeviceDriver.IsSupported;
|
||||||
sdl2IsSupported = SDL2HardwareDeviceDriver.IsSupported;
|
sdl2IsSupported = SDL2HardwareDeviceDriver.IsSupported;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -438,6 +438,15 @@ namespace Ryujinx.Ui.Windows
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
_ => 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()
|
private void UpdatePreferredGpuComboBox()
|
||||||
|
|
|
@ -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) { }
|
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;
|
_parent = parent;
|
||||||
|
|
||||||
|
|
Reference in a new issue