infra: Make Avalonia the default UI (#6375)

* misc: Move Ryujinx project to Ryujinx.Gtk3

This breaks release CI for now but that's fine.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* misc: Move Ryujinx.Ava project to Ryujinx

This breaks CI for now, but it's fine.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* infra: Make Avalonia the default UI

Should fix CI after the previous changes.

GTK3 isn't build by the release job anymore, only by PR CI.

This also ensure that the test-ava update package is still generated to
allow update from the old testing channel.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* Fix missing copy in create_app_bundle.sh

Signed-off-by: Mary Guillemard <mary@mary.zone>

* Fix syntax error

Signed-off-by: Mary Guillemard <mary@mary.zone>

---------

Signed-off-by: Mary Guillemard <mary@mary.zone>
This commit is contained in:
Mary Guillemard 2024-03-02 12:51:05 +01:00 committed by GitHub
parent 53b5985da6
commit ec6cb0abb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
239 changed files with 1235 additions and 1232 deletions

View file

@ -0,0 +1,294 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform;
using Ryujinx.Common.Configuration;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.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;
using System.Threading.Tasks;
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
namespace Ryujinx.Ava.UI.Renderer
{
public class EmbeddedWindow : NativeControlHost
{
private WindowProc _wndProcDelegate;
private string _className;
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; }
public delegate void UpdateBoundsCallbackDelegate(Rect rect);
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
public event EventHandler<IntPtr> WindowCreated;
public event EventHandler<Size> BoundsChanged;
public EmbeddedWindow()
{
this.GetObservable(BoundsProperty).Subscribe(StateChanged);
Initialized += OnNativeEmbeddedWindowCreated;
}
public virtual void OnWindowCreated() { }
protected virtual void OnWindowDestroyed() { }
protected virtual void OnWindowDestroying()
{
WindowHandle = IntPtr.Zero;
X11Display = IntPtr.Zero;
NsView = IntPtr.Zero;
MetalLayer = IntPtr.Zero;
}
private void OnNativeEmbeddedWindowCreated(object sender, EventArgs e)
{
OnWindowCreated();
Task.Run(() =>
{
WindowCreated?.Invoke(this, WindowHandle);
});
}
private void StateChanged(Rect rect)
{
BoundsChanged?.Invoke(this, rect.Size);
_updateBoundsCallback?.Invoke(rect);
}
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle control)
{
if (OperatingSystem.IsLinux())
{
return CreateLinux(control);
}
if (OperatingSystem.IsWindows())
{
return CreateWin32(control);
}
if (OperatingSystem.IsMacOS())
{
return CreateMacOS();
}
return base.CreateNativeControlCore(control);
}
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
OnWindowDestroying();
if (OperatingSystem.IsLinux())
{
DestroyLinux();
}
else if (OperatingSystem.IsWindows())
{
DestroyWin32(control);
}
else if (OperatingSystem.IsMacOS())
{
DestroyMacOS();
}
else
{
base.DestroyNativeControlCore(control);
}
OnWindowDestroyed();
}
[SupportedOSPlatform("linux")]
private IPlatformHandle CreateLinux(IPlatformHandle control)
{
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan)
{
X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(control.Handle));
X11Window.Hide();
}
else
{
X11Window = PlatformHelper.CreateOpenGLWindow(new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false), 0, 0, 100, 100) as GLXWindow;
}
WindowHandle = X11Window.WindowHandle.RawHandle;
X11Display = X11Window.DisplayHandle.RawHandle;
return new PlatformHandle(WindowHandle, "X11");
}
[SupportedOSPlatform("windows")]
IPlatformHandle CreateWin32(IPlatformHandle control)
{
_className = "NativeWindow-" + Guid.NewGuid();
_wndProcDelegate = delegate (IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
{
if (VisualRoot != null)
{
if (msg == WindowsMessages.Lbuttondown ||
msg == WindowsMessages.Rbuttondown ||
msg == WindowsMessages.Lbuttonup ||
msg == WindowsMessages.Rbuttonup ||
msg == WindowsMessages.Mousemove)
{
Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), this).Value;
Pointer pointer = new(0, PointerType.Mouse, true);
#pragma warning disable CS0618 // Type or member is obsolete (As of Avalonia 11, the constructors for PointerPressedEventArgs & PointerEventArgs are marked as obsolete)
switch (msg)
{
case WindowsMessages.Lbuttondown:
case WindowsMessages.Rbuttondown:
{
bool isLeft = msg == WindowsMessages.Lbuttondown;
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed);
var evnt = new PointerPressedEventArgs(
this,
pointer,
this,
rootVisualPosition,
(ulong)Environment.TickCount64,
properties,
KeyModifiers.None);
RaiseEvent(evnt);
break;
}
case WindowsMessages.Lbuttonup:
case WindowsMessages.Rbuttonup:
{
bool isLeft = msg == WindowsMessages.Lbuttonup;
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased);
var evnt = new PointerReleasedEventArgs(
this,
pointer,
this,
rootVisualPosition,
(ulong)Environment.TickCount64,
properties,
KeyModifiers.None,
isLeft ? MouseButton.Left : MouseButton.Right);
RaiseEvent(evnt);
break;
}
case WindowsMessages.Mousemove:
{
var evnt = new PointerEventArgs(
PointerMovedEvent,
this,
pointer,
this,
rootVisualPosition,
(ulong)Environment.TickCount64,
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
KeyModifiers.None);
RaiseEvent(evnt);
break;
}
}
#pragma warning restore CS0618
}
}
return DefWindowProc(hWnd, msg, wParam, lParam);
};
WndClassEx wndClassEx = new()
{
cbSize = Marshal.SizeOf<WndClassEx>(),
hInstance = GetModuleHandle(null),
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
style = ClassStyles.CsOwndc,
lpszClassName = Marshal.StringToHGlobalUni(_className),
hCursor = CreateArrowCursor(),
};
RegisterClassEx(ref wndClassEx);
WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WsChild, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
Marshal.FreeHGlobal(wndClassEx.lpszClassName);
return new PlatformHandle(WindowHandle, "HWND");
}
[SupportedOSPlatform("macos")]
IPlatformHandle CreateMacOS()
{
// Create a new CAMetalLayer.
ObjectiveC.Object layerObject = new("CAMetalLayer");
ObjectiveC.Object metalLayer = layerObject.GetFromMessage("alloc");
metalLayer.SendMessage("init");
// Create a child NSView to render into.
ObjectiveC.Object nsViewObject = new("NSView");
ObjectiveC.Object child = nsViewObject.GetFromMessage("alloc");
child.SendMessage("init", new ObjectiveC.NSRect(0, 0, 0, 0));
// Make its renderer our metal layer.
child.SendMessage("setWantsLayer:", 1);
child.SendMessage("setLayer:", metalLayer);
metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor);
// Ensure the scale factor is up to date.
_updateBoundsCallback = rect =>
{
metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor);
};
IntPtr nsView = child.ObjPtr;
MetalLayer = metalLayer.ObjPtr;
NsView = nsView;
return new PlatformHandle(nsView, "NSView");
}
[SupportedOSPlatform("Linux")]
void DestroyLinux()
{
X11Window?.Dispose();
}
[SupportedOSPlatform("windows")]
void DestroyWin32(IPlatformHandle handle)
{
DestroyWindow(handle.Handle);
UnregisterClass(_className, GetModuleHandle(null));
}
[SupportedOSPlatform("macos")]
#pragma warning disable CA1822 // Mark member as static
void DestroyMacOS()
{
// TODO
}
#pragma warning restore CA1822
}
}

View file

@ -0,0 +1,94 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.UI.Common.Configuration;
using SPB.Graphics;
using SPB.Graphics.Exceptions;
using SPB.Graphics.OpenGL;
using SPB.Platform;
using SPB.Platform.WGL;
using SPB.Windowing;
using System;
namespace Ryujinx.Ava.UI.Renderer
{
public class EmbeddedWindowOpenGL : EmbeddedWindow
{
private SwappableNativeWindowBase _window;
public OpenGLContextBase Context { get; set; }
protected override void OnWindowDestroying()
{
Context.Dispose();
base.OnWindowDestroying();
}
public override void OnWindowCreated()
{
base.OnWindowCreated();
if (OperatingSystem.IsWindows())
{
_window = new WGLWindow(new NativeHandle(WindowHandle));
}
else if (OperatingSystem.IsLinux())
{
_window = X11Window;
}
else
{
throw new PlatformNotSupportedException();
}
var flags = OpenGLContextFlags.Compat;
if (ConfigurationState.Instance.Logger.GraphicsDebugLevel != GraphicsDebugLevel.None)
{
flags |= OpenGLContextFlags.Debug;
}
var graphicsMode = Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default;
Context = PlatformHelper.CreateOpenGLContext(graphicsMode, 3, 3, flags);
Context.Initialize(_window);
Context.MakeCurrent(_window);
GL.LoadBindings(new OpenTKBindingsContext(Context.GetProcAddress));
Context.MakeCurrent(null);
}
public void MakeCurrent(bool unbind = false, bool shouldThrow = true)
{
try
{
Context?.MakeCurrent(!unbind ? _window : null);
}
catch (ContextException e)
{
if (shouldThrow)
{
throw;
}
Logger.Warning?.Print(LogClass.UI, $"Failed to {(!unbind ? "bind" : "unbind")} OpenGL context: {e}");
}
}
public void SwapBuffers()
{
_window?.SwapBuffers();
}
public void InitializeBackgroundContext(IRenderer renderer)
{
(renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Context));
MakeCurrent();
}
}
}

View file

@ -0,0 +1,42 @@
using Silk.NET.Vulkan;
using SPB.Graphics.Vulkan;
using SPB.Platform.Metal;
using SPB.Platform.Win32;
using SPB.Platform.X11;
using SPB.Windowing;
using System;
namespace Ryujinx.Ava.UI.Renderer
{
public class EmbeddedWindowVulkan : EmbeddedWindow
{
public SurfaceKHR CreateSurface(Instance instance)
{
NativeWindowBase nativeWindowBase;
if (OperatingSystem.IsWindows())
{
nativeWindowBase = new SimpleWin32Window(new NativeHandle(WindowHandle));
}
else if (OperatingSystem.IsLinux())
{
nativeWindowBase = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
}
else if (OperatingSystem.IsMacOS())
{
nativeWindowBase = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
}
else
{
throw new PlatformNotSupportedException();
}
return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, nativeWindowBase));
}
public SurfaceKHR CreateSurface(Instance instance, Vk _)
{
return CreateSurface(instance);
}
}
}

View file

@ -0,0 +1,20 @@
using OpenTK;
using System;
namespace Ryujinx.Ava.UI.Renderer
{
internal class OpenTKBindingsContext : IBindingsContext
{
private readonly Func<string, IntPtr> _getProcAddress;
public OpenTKBindingsContext(Func<string, IntPtr> getProcAddress)
{
_getProcAddress = getProcAddress;
}
public IntPtr GetProcAddress(string procName)
{
return _getProcAddress(procName);
}
}
}

View file

@ -0,0 +1,12 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
x:Class="Ryujinx.Ava.UI.Renderer.RendererHost"
FlowDirection="LeftToRight"
Focusable="True">
</UserControl>

View file

@ -0,0 +1,68 @@
using Avalonia;
using Avalonia.Controls;
using Ryujinx.Common.Configuration;
using Ryujinx.UI.Common.Configuration;
using System;
namespace Ryujinx.Ava.UI.Renderer
{
public partial class RendererHost : UserControl, IDisposable
{
public readonly EmbeddedWindow EmbeddedWindow;
public event EventHandler<EventArgs> WindowCreated;
public event Action<object, Size> BoundsChanged;
public RendererHost()
{
InitializeComponent();
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
{
EmbeddedWindow = new EmbeddedWindowOpenGL();
}
else
{
EmbeddedWindow = new EmbeddedWindowVulkan();
}
Initialize();
}
private void Initialize()
{
EmbeddedWindow.WindowCreated += CurrentWindow_WindowCreated;
EmbeddedWindow.BoundsChanged += CurrentWindow_BoundsChanged;
Content = EmbeddedWindow;
}
public void Dispose()
{
if (EmbeddedWindow != null)
{
EmbeddedWindow.WindowCreated -= CurrentWindow_WindowCreated;
EmbeddedWindow.BoundsChanged -= CurrentWindow_BoundsChanged;
}
GC.SuppressFinalize(this);
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
Dispose();
}
private void CurrentWindow_BoundsChanged(object sender, Size e)
{
BoundsChanged?.Invoke(sender, e);
}
private void CurrentWindow_WindowCreated(object sender, IntPtr e)
{
WindowCreated?.Invoke(this, EventArgs.Empty);
}
}
}

View file

@ -0,0 +1,49 @@
using OpenTK.Graphics.OpenGL;
using Ryujinx.Graphics.OpenGL;
using SPB.Graphics;
using SPB.Graphics.OpenGL;
using SPB.Platform;
using SPB.Windowing;
namespace Ryujinx.Ava.UI.Renderer
{
class SPBOpenGLContext : IOpenGLContext
{
private readonly OpenGLContextBase _context;
private readonly NativeWindowBase _window;
private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window)
{
_context = context;
_window = window;
}
public void Dispose()
{
_context.Dispose();
_window.Dispose();
}
public void MakeCurrent()
{
_context.MakeCurrent(_window);
}
public bool HasContext() => _context.IsCurrent;
public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
{
OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100);
context.Initialize(window);
context.MakeCurrent(window);
GL.LoadBindings(new OpenTKBindingsContext(context.GetProcAddress));
context.MakeCurrent(null);
return new SPBOpenGLContext(context, window);
}
}
}