diff options
| author | Emmanuel Hansen <emmausssss@gmail.com> | 2022-09-19 18:05:26 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-09-19 15:05:26 -0300 |
| commit | 6f0395538b8e8af3bba7536b44780d57e51e8697 (patch) | |
| tree | 4d3f4f620dd287fc1ca38ea9ea722b6e022301dd /Ryujinx.Ava/Ui/Controls | |
| parent | b9f1ff3c7748c6a2665e76d17e86c3b7228f44fe (diff) | |
Avalonia - Use embedded window for avalonia (#3674)
* wip
* use embedded window
* fix race condition on opengl Windows
* fix glx issues on prime nvidia
* fix mouse support win32
* clean up
* addressed review
* addressed review
* fix warnings
* fix sotware keyboard dialog
* Update Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
* remove double semi
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
Diffstat (limited to 'Ryujinx.Ava/Ui/Controls')
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs | 78 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs | 204 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/OpenGLEmbeddedWindow.cs | 85 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs | 192 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/RendererControl.cs | 96 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/RendererHost.axaml | 14 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/RendererHost.axaml.cs | 126 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs | 33 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs | 220 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Controls/Win32NativeInterop.cs | 113 |
10 files changed, 650 insertions, 511 deletions
diff --git a/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs b/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs index 15ecaa77..e774a09a 100644 --- a/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs +++ b/Ryujinx.Ava/Ui/Controls/ContentDialogHelper.cs @@ -1,4 +1,8 @@ +using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Controls.Primitives; +using Avalonia.Media; using Avalonia.Threading; using FluentAvalonia.UI.Controls; using Ryujinx.Ava.Common.Locale; @@ -27,9 +31,69 @@ namespace Ryujinx.Ava.Ui.Controls { UserResult result = UserResult.None; - ContentDialog contentDialog = new ContentDialog(); + bool useOverlay = false; + Window mainWindow = null; - await ShowDialog(); + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime al) + { + foreach (var item in al.Windows) + { + if (item.IsActive && item is MainWindow window && window.ViewModel.IsGameRunning) + { + mainWindow = window; + useOverlay = true; + break; + } + } + } + + ContentDialog contentDialog = null; + ContentDialogOverlayWindow overlay = null; + + if (useOverlay) + { + overlay = new ContentDialogOverlayWindow() + { + Height = mainWindow.Bounds.Height, + Width = mainWindow.Bounds.Width, + Position = mainWindow.PointToScreen(new Point()) + }; + + mainWindow.PositionChanged += OverlayOnPositionChanged; + + void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) + { + overlay.Position = mainWindow.PointToScreen(new Point()); + } + + contentDialog = overlay.ContentDialog; + + bool opened = false; + + overlay.Opened += OverlayOnActivated; + + async void OverlayOnActivated(object sender, EventArgs e) + { + if (opened) + { + return; + } + + opened = true; + + overlay.Position = mainWindow.PointToScreen(new Point()); + + await ShowDialog(); + } + + await overlay.ShowDialog(mainWindow); + } + else + { + contentDialog = new ContentDialog(); + + await ShowDialog(); + } async Task ShowDialog() { @@ -53,6 +117,14 @@ namespace Ryujinx.Ava.Ui.Controls }); await contentDialog.ShowAsync(ContentDialogPlacement.Popup); + + overlay?.Close(); + } + + if (useOverlay) + { + overlay.Content = null; + overlay.Close(); } return result; @@ -323,4 +395,4 @@ namespace Ryujinx.Ava.Ui.Controls return string.Empty; } } -}
\ No newline at end of file +} diff --git a/Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs b/Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs new file mode 100644 index 00000000..d9fae93a --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs @@ -0,0 +1,204 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Platform; +using SPB.Graphics; +using SPB.Platform; +using SPB.Platform.GLX; +using SPB.Platform.X11; +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Threading.Tasks; +using static Ryujinx.Ava.Ui.Controls.Win32NativeInterop; + +namespace Ryujinx.Ava.Ui.Controls +{ + public unsafe class EmbeddedWindow : NativeControlHost + { + private WindowProc _wndProcDelegate; + private string _className; + + protected GLXWindow X11Window { get; private set; } + protected IntPtr WindowHandle { get; set; } + protected IntPtr X11Display { get; set; } + + public event EventHandler<IntPtr> WindowCreated; + public event EventHandler<Size> SizeChanged; + + protected virtual void OnWindowDestroyed() { } + protected virtual void OnWindowDestroying() + { + WindowHandle = IntPtr.Zero; + X11Display = IntPtr.Zero; + } + + public EmbeddedWindow() + { + var stateObserverable = this.GetObservable(Control.BoundsProperty); + + stateObserverable.Subscribe(StateChanged); + + this.Initialized += NativeEmbeddedWindow_Initialized; + } + + public virtual void OnWindowCreated() { } + + private void NativeEmbeddedWindow_Initialized(object sender, EventArgs e) + { + OnWindowCreated(); + + Task.Run(() => + { + WindowCreated?.Invoke(this, WindowHandle); + }); + } + + private void StateChanged(Rect rect) + { + SizeChanged?.Invoke(this, rect.Size); + } + + protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent) + { + if (OperatingSystem.IsLinux()) + { + return CreateLinux(parent); + } + else if (OperatingSystem.IsWindows()) + { + return CreateWin32(parent); + } + return base.CreateNativeControlCore(parent); + } + + protected override void DestroyNativeControlCore(IPlatformHandle control) + { + OnWindowDestroying(); + + if (OperatingSystem.IsLinux()) + { + DestroyLinux(); + } + else if (OperatingSystem.IsWindows()) + { + DestroyWin32(control); + } + else + { + base.DestroyNativeControlCore(control); + } + + OnWindowDestroyed(); + } + + [SupportedOSPlatform("linux")] + IPlatformHandle CreateLinux(IPlatformHandle parent) + { + X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow; + + WindowHandle = X11Window.WindowHandle.RawHandle; + + X11Display = X11Window.DisplayHandle.RawHandle; + + return new PlatformHandle(WindowHandle, "X11"); + } + + [SupportedOSPlatform("windows")] + unsafe IPlatformHandle CreateWin32(IPlatformHandle parent) + { + _className = "NativeWindow-" + Guid.NewGuid(); + _wndProcDelegate = WndProc; + var wndClassEx = new WNDCLASSEX + { + cbSize = Marshal.SizeOf<WNDCLASSEX>(), + hInstance = GetModuleHandle(null), + lpfnWndProc = _wndProcDelegate, + style = ClassStyles.CS_OWNDC, + lpszClassName = _className, + hCursor = LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW) + }; + + var atom = RegisterClassEx(ref wndClassEx); + + var handle = CreateWindowEx( + 0, + _className, + "NativeWindow", + WindowStyles.WS_CHILD, + 0, + 0, + 640, + 480, + parent.Handle, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero); + + WindowHandle = handle; + + return new PlatformHandle(WindowHandle, "HWND"); + } + + [SupportedOSPlatform("windows")] + internal IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam) + { + var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF); + var root = VisualRoot as Window; + bool isLeft = false; + switch (msg) + { + case WindowsMessages.LBUTTONDOWN: + case WindowsMessages.RBUTTONDOWN: + isLeft = msg == WindowsMessages.LBUTTONDOWN; + this.RaiseEvent(new PointerPressedEventArgs( + this, + new Avalonia.Input.Pointer(0, PointerType.Mouse, true), + root, + this.TranslatePoint(point, root).Value, + (ulong)Environment.TickCount64, + new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed), + KeyModifiers.None)); + break; + case WindowsMessages.LBUTTONUP: + case WindowsMessages.RBUTTONUP: + isLeft = msg == WindowsMessages.LBUTTONUP; + this.RaiseEvent(new PointerReleasedEventArgs( + this, + new Avalonia.Input.Pointer(0, PointerType.Mouse, true), + root, + this.TranslatePoint(point, root).Value, + (ulong)Environment.TickCount64, + new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased), + KeyModifiers.None, + isLeft ? MouseButton.Left : MouseButton.Right)); + break; + case WindowsMessages.MOUSEMOVE: + this.RaiseEvent(new PointerEventArgs( + PointerMovedEvent, + this, + new Avalonia.Input.Pointer(0, PointerType.Mouse, true), + root, + this.TranslatePoint(point, root).Value, + (ulong)Environment.TickCount64, + new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other), + KeyModifiers.None)); + break; + } + return DefWindowProc(hWnd, msg, (IntPtr)wParam, (IntPtr)lParam); + } + + void DestroyLinux() + { + X11Window?.Dispose(); + } + + [SupportedOSPlatform("windows")] + void DestroyWin32(IPlatformHandle handle) + { + DestroyWindow(handle.Handle); + UnregisterClass(_className, GetModuleHandle(null)); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/OpenGLEmbeddedWindow.cs b/Ryujinx.Ava/Ui/Controls/OpenGLEmbeddedWindow.cs new file mode 100644 index 00000000..ce579fdf --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/OpenGLEmbeddedWindow.cs @@ -0,0 +1,85 @@ +using Avalonia; +using Avalonia.OpenGL; +using OpenTK.Graphics.OpenGL; +using Ryujinx.Common.Configuration; +using SPB.Graphics; +using SPB.Graphics.OpenGL; +using SPB.Platform; +using SPB.Platform.GLX; +using SPB.Platform.WGL; +using SPB.Windowing; +using System; + +namespace Ryujinx.Ava.Ui.Controls +{ + public class OpenGLEmbeddedWindow : EmbeddedWindow + { + private readonly int _major; + private readonly int _minor; + private readonly GraphicsDebugLevel _graphicsDebugLevel; + private SwappableNativeWindowBase _window; + public OpenGLContextBase Context { get; set; } + + public OpenGLEmbeddedWindow(int major, int minor, GraphicsDebugLevel graphicsDebugLevel) + { + _major = major; + _minor = minor; + _graphicsDebugLevel = graphicsDebugLevel; + } + + 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 (_graphicsDebugLevel != GraphicsDebugLevel.None) + { + flags |= OpenGLContextFlags.Debug; + } + + Context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, _major, _minor, flags); + + Context.Initialize(_window); + Context.MakeCurrent(_window); + + var bindingsContext = new OpenToolkitBindingsContext(Context.GetProcAddress); + + GL.LoadBindings(bindingsContext); + Context.MakeCurrent(null); + } + + public void MakeCurrent() + { + Context.MakeCurrent(_window); + } + + public void MakeCurrent(NativeWindowBase window) + { + Context.MakeCurrent(window); + } + + public void SwapBuffers() + { + _window.SwapBuffers(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs b/Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs deleted file mode 100644 index e58bdaa0..00000000 --- a/Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs +++ /dev/null @@ -1,192 +0,0 @@ -using Avalonia; -using Avalonia.OpenGL; -using Avalonia.Platform; -using Avalonia.Rendering.SceneGraph; -using Avalonia.Skia; -using Avalonia.Threading; -using OpenTK.Graphics.OpenGL; -using Ryujinx.Common.Configuration; -using SkiaSharp; -using SPB.Graphics; -using SPB.Graphics.OpenGL; -using SPB.Platform; -using SPB.Windowing; -using System; - -namespace Ryujinx.Ava.Ui.Controls -{ - internal class OpenGLRendererControl : RendererControl - { - public int Major { get; } - public int Minor { get; } - public OpenGLContextBase GameContext { get; set; } - - public static OpenGLContextBase PrimaryContext => - AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>() - .PrimaryContext.AsOpenGLContextBase(); - - private SwappableNativeWindowBase _gameBackgroundWindow; - - private IntPtr _fence; - - public OpenGLRendererControl(int major, int minor, GraphicsDebugLevel graphicsDebugLevel) : base(graphicsDebugLevel) - { - Major = major; - Minor = minor; - } - - public override void DestroyBackgroundContext() - { - Image = null; - - if (_fence != IntPtr.Zero) - { - DrawOperation.Dispose(); - GL.DeleteSync(_fence); - } - - GlDrawOperation.DeleteFramebuffer(); - - GameContext?.Dispose(); - - _gameBackgroundWindow?.Dispose(); - } - - internal override void Present(object image) - { - Dispatcher.UIThread.InvokeAsync(() => - { - Image = (int)image; - - InvalidateVisual(); - }).Wait(); - - if (_fence != IntPtr.Zero) - { - GL.DeleteSync(_fence); - } - - _fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None); - - InvalidateVisual(); - - _gameBackgroundWindow.SwapBuffers(); - } - - internal override void MakeCurrent() - { - GameContext.MakeCurrent(_gameBackgroundWindow); - } - - internal override void MakeCurrent(SwappableNativeWindowBase window) - { - GameContext.MakeCurrent(window); - } - - protected override void CreateWindow() - { - var flags = OpenGLContextFlags.Compat; - if (DebugLevel != GraphicsDebugLevel.None) - { - flags |= OpenGLContextFlags.Debug; - } - _gameBackgroundWindow = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100); - _gameBackgroundWindow.Hide(); - - GameContext = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, Major, Minor, flags, shareContext: PrimaryContext); - GameContext.Initialize(_gameBackgroundWindow); - } - - protected override ICustomDrawOperation CreateDrawOperation() - { - return new GlDrawOperation(this); - } - - private class GlDrawOperation : ICustomDrawOperation - { - private static int _framebuffer; - - public Rect Bounds { get; } - - private readonly OpenGLRendererControl _control; - - public GlDrawOperation(OpenGLRendererControl control) - { - _control = control; - Bounds = _control.Bounds; - } - - public void Dispose() { } - - public static void DeleteFramebuffer() - { - if (_framebuffer == 0) - { - GL.DeleteFramebuffer(_framebuffer); - } - - _framebuffer = 0; - } - - public bool Equals(ICustomDrawOperation other) - { - return other is GlDrawOperation operation && Equals(this, operation) && operation.Bounds == Bounds; - } - - public bool HitTest(Point p) - { - return Bounds.Contains(p); - } - - private void CreateRenderTarget() - { - _framebuffer = GL.GenFramebuffer(); - } - - public void Render(IDrawingContextImpl context) - { - if (_control.Image == null) - { - return; - } - - if (_framebuffer == 0) - { - CreateRenderTarget(); - } - - int currentFramebuffer = GL.GetInteger(GetPName.FramebufferBinding); - - var image = _control.Image; - var fence = _control._fence; - - GL.BindFramebuffer(FramebufferTarget.Framebuffer, _framebuffer); - GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, (int)image, 0); - GL.BindFramebuffer(FramebufferTarget.Framebuffer, currentFramebuffer); - - if (context is not ISkiaDrawingContextImpl skiaDrawingContextImpl) - { - return; - } - - var imageInfo = new SKImageInfo((int)_control.RenderSize.Width, (int)_control.RenderSize.Height, SKColorType.Rgba8888); - var glInfo = new GRGlFramebufferInfo((uint)_framebuffer, SKColorType.Rgba8888.ToGlSizedFormat()); - - GL.WaitSync(fence, WaitSyncFlags.None, ulong.MaxValue); - - using var backendTexture = new GRBackendRenderTarget(imageInfo.Width, imageInfo.Height, 1, 0, glInfo); - using var surface = SKSurface.Create(skiaDrawingContextImpl.GrContext, backendTexture, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888); - - if (surface == null) - { - return; - } - - var rect = new Rect(new Point(), _control.RenderSize); - - using var snapshot = surface.Snapshot(); - skiaDrawingContextImpl.SkCanvas.DrawImage(snapshot, rect.ToSKRect(), _control.Bounds.ToSKRect(), new SKPaint()); - } - } - } -} diff --git a/Ryujinx.Ava/Ui/Controls/RendererControl.cs b/Ryujinx.Ava/Ui/Controls/RendererControl.cs deleted file mode 100644 index 392f67e3..00000000 --- a/Ryujinx.Ava/Ui/Controls/RendererControl.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Data; -using Avalonia.Media; -using Avalonia.Rendering.SceneGraph; -using Ryujinx.Common.Configuration; -using SPB.Windowing; -using System; - -namespace Ryujinx.Ava.Ui.Controls -{ - internal abstract class RendererControl : Control - { - protected object Image { get; set; } - - public event EventHandler<EventArgs> RendererInitialized; - public event EventHandler<Size> SizeChanged; - - protected Size RenderSize { get; private set; } - public bool IsStarted { get; private set; } - - public GraphicsDebugLevel DebugLevel { get; } - - private bool _isInitialized; - - protected ICustomDrawOperation DrawOperation { get; private set; } - - public RendererControl(GraphicsDebugLevel graphicsDebugLevel) - { - DebugLevel = graphicsDebugLevel; - IObservable<Rect> resizeObservable = this.GetObservable(BoundsProperty); - - resizeObservable.Subscribe(Resized); - - Focusable = true; - } - - protected void Resized(Rect rect) - { - SizeChanged?.Invoke(this, rect.Size); - - if (!rect.IsEmpty) - { - RenderSize = rect.Size * VisualRoot.RenderScaling; - DrawOperation = CreateDrawOperation(); - } - } - - protected abstract ICustomDrawOperation CreateDrawOperation(); - protected abstract void CreateWindow(); - - public override void Render(DrawingContext context) - { - if (!_isInitialized) - { - CreateWindow(); - - OnRendererInitialized(); - _isInitialized = true; - } - - if (!IsStarted || Image == null) - { - return; - } - - if (DrawOperation != null) - { - context.Custom(DrawOperation); - } - - base.Render(context); - } - - protected void OnRendererInitialized() - { - RendererInitialized?.Invoke(this, EventArgs.Empty); - } - - internal abstract void Present(object image); - - internal void Start() - { - IsStarted = true; - } - - internal void Stop() - { - IsStarted = false; - } - - public abstract void DestroyBackgroundContext(); - internal abstract void MakeCurrent(); - internal abstract void MakeCurrent(SwappableNativeWindowBase window); - } -}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/RendererHost.axaml b/Ryujinx.Ava/Ui/Controls/RendererHost.axaml new file mode 100644 index 00000000..be72fd61 --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/RendererHost.axaml @@ -0,0 +1,14 @@ +<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.Controls.RendererHost"> + <ContentControl + HorizontalAlignment="Stretch" + HorizontalContentAlignment="Stretch" + VerticalAlignment="Stretch" + VerticalContentAlignment="Stretch" + Name="View" + /> +</UserControl> diff --git a/Ryujinx.Ava/Ui/Controls/RendererHost.axaml.cs b/Ryujinx.Ava/Ui/Controls/RendererHost.axaml.cs new file mode 100644 index 00000000..0d1984fd --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/RendererHost.axaml.cs @@ -0,0 +1,126 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Ryujinx.Common.Configuration; +using Silk.NET.Vulkan; +using SPB.Graphics.OpenGL; +using SPB.Windowing; +using System; + +namespace Ryujinx.Ava.Ui.Controls +{ + public partial class RendererHost : UserControl, IDisposable + { + private readonly GraphicsDebugLevel _graphicsDebugLevel; + private EmbeddedWindow _currentWindow; + + public bool IsVulkan { get; private set; } + + public RendererHost(GraphicsDebugLevel graphicsDebugLevel) + { + _graphicsDebugLevel = graphicsDebugLevel; + InitializeComponent(); + } + + public RendererHost() + { + InitializeComponent(); + } + + public void CreateOpenGL() + { + Dispose(); + + _currentWindow = new OpenGLEmbeddedWindow(3, 3, _graphicsDebugLevel); + Initialize(); + + IsVulkan = false; + } + + private void Initialize() + { + _currentWindow.WindowCreated += CurrentWindow_WindowCreated; + _currentWindow.SizeChanged += CurrentWindow_SizeChanged; + View.Content = _currentWindow; + } + + public void CreateVulkan() + { + Dispose(); + + _currentWindow = new VulkanEmbeddedWindow(); + Initialize(); + + IsVulkan = true; + } + + public OpenGLContextBase GetContext() + { + if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow) + { + return openGlEmbeddedWindow.Context; + } + + return null; + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + + Dispose(); + } + + private void CurrentWindow_SizeChanged(object sender, Size e) + { + SizeChanged?.Invoke(sender, e); + } + + private void CurrentWindow_WindowCreated(object sender, IntPtr e) + { + RendererInitialized?.Invoke(this, EventArgs.Empty); + } + + public void MakeCurrent() + { + if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow) + { + openGlEmbeddedWindow.MakeCurrent(); + } + } + + public void MakeCurrent(SwappableNativeWindowBase window) + { + if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow) + { + openGlEmbeddedWindow.MakeCurrent(window); + } + } + + public void SwapBuffers() + { + if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow) + { + openGlEmbeddedWindow.SwapBuffers(); + } + } + + public event EventHandler<EventArgs> RendererInitialized; + public event Action<object, Size> SizeChanged; + public void Dispose() + { + if (_currentWindow != null) + { + _currentWindow.WindowCreated -= CurrentWindow_WindowCreated; + _currentWindow.SizeChanged -= CurrentWindow_SizeChanged; + } + } + + public SurfaceKHR CreateVulkanSurface(Instance instance, Vk api) + { + return (_currentWindow is VulkanEmbeddedWindow vulkanEmbeddedWindow) + ? vulkanEmbeddedWindow.CreateSurface(instance) + : default; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs b/Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs new file mode 100644 index 00000000..d2c980dd --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs @@ -0,0 +1,33 @@ +using Ryujinx.Ava.Ui.Controls; +using Silk.NET.Vulkan; +using SPB.Graphics.Vulkan; +using SPB.Platform.Win32; +using SPB.Platform.X11; +using SPB.Windowing; +using System; + +namespace Ryujinx.Ava.Ui +{ + public class VulkanEmbeddedWindow : EmbeddedWindow + { + private NativeWindowBase _window; + + public SurfaceKHR CreateSurface(Instance instance) + { + if (OperatingSystem.IsWindows()) + { + _window = new SimpleWin32Window(new NativeHandle(WindowHandle)); + } + else if (OperatingSystem.IsLinux()) + { + _window = X11Window; + } + else + { + throw new PlatformNotSupportedException(); + } + + return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, _window)); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs b/Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs deleted file mode 100644 index 7b7dfaa1..00000000 --- a/Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs +++ /dev/null @@ -1,220 +0,0 @@ -using Avalonia; -using Avalonia.Media; -using Avalonia.Platform; -using Avalonia.Rendering.SceneGraph; -using Avalonia.Skia; -using Avalonia.Threading; -using Ryujinx.Ava.Ui.Backend.Vulkan; -using Ryujinx.Ava.Ui.Vulkan; -using Ryujinx.Common.Configuration; -using Ryujinx.Graphics.Vulkan; -using Silk.NET.Vulkan; -using SkiaSharp; -using SPB.Windowing; -using System; -using System.Collections.Concurrent; - -namespace Ryujinx.Ava.Ui.Controls -{ - internal class VulkanRendererControl : RendererControl - { - private const int MaxImagesInFlight = 3; - - private VulkanPlatformInterface _platformInterface; - private ConcurrentQueue<PresentImageInfo> _imagesInFlight; - private PresentImageInfo _currentImage; - - public VulkanRendererControl(GraphicsDebugLevel graphicsDebugLevel) : base(graphicsDebugLevel) - { - _platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>(); - - _imagesInFlight = new ConcurrentQueue<PresentImageInfo>(); - } - - public override void DestroyBackgroundContext() - { - - } - - protected override ICustomDrawOperation CreateDrawOperation() - { - return new VulkanDrawOperation(this); - } - - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnDetachedFromVisualTree(e); - - _imagesInFlight.Clear(); - - if (_platformInterface.MainSurface.Display != null) - { - _platformInterface.MainSurface.Display.Presented -= Window_Presented; - } - - _currentImage?.Put(); - _currentImage = null; - } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - _platformInterface.MainSurface.Display.Presented += Window_Presented; - } - - private void Window_Presented(object sender, EventArgs e) - { - _platformInterface.MainSurface.Device.QueueWaitIdle(); - _currentImage?.Put(); - _currentImage = null; - } - - public override void Render(DrawingContext context) - { - base.Render(context); - } - - protected override void CreateWindow() - { - } - - internal override void MakeCurrent() - { - } - - internal override void MakeCurrent(SwappableNativeWindowBase window) - { - } - - internal override void Present(object image) - { - Image = image; - - _imagesInFlight.Enqueue((PresentImageInfo)image); - - if (_imagesInFlight.Count > MaxImagesInFlight) - { - _imagesInFlight.TryDequeue(out _); - } - - Dispatcher.UIThread.Post(InvalidateVisual); - } - - private PresentImageInfo GetImage() - { - lock (_imagesInFlight) - { - if (!_imagesInFlight.TryDequeue(out _currentImage)) - { - _currentImage = (PresentImageInfo)Image; - } - - return _currentImage; - } - } - - private class VulkanDrawOperation : ICustomDrawOperation - { - public Rect Bounds { get; } - - private readonly VulkanRendererControl _control; - private bool _isDestroyed; - - public VulkanDrawOperation(VulkanRendererControl control) - { - _control = control; - Bounds = _control.Bounds; - } - - public void Dispose() - { - if (_isDestroyed) - { - return; - } - - _isDestroyed = true; - } - - public bool Equals(ICustomDrawOperation other) - { - return other is VulkanDrawOperation operation && Equals(this, operation) && operation.Bounds == Bounds; - } - - public bool HitTest(Point p) - { - return Bounds.Contains(p); - } - - public unsafe void Render(IDrawingContextImpl context) - { - if (_isDestroyed || _control.Image == null || _control.RenderSize.Width == 0 || _control.RenderSize.Height == 0 || - context is not ISkiaDrawingContextImpl skiaDrawingContextImpl) - { - return; - } - - var image = _control.GetImage(); - - if (!image.State.IsValid) - { - _control._currentImage = null; - - return; - } - - var gpu = AvaloniaLocator.Current.GetService<VulkanSkiaGpu>(); - - image.Get(); - - var imageInfo = new GRVkImageInfo() - { - CurrentQueueFamily = _control._platformInterface.PhysicalDevice.QueueFamilyIndex, - Format = (uint)Format.R8G8B8A8Unorm, - Image = image.Image.Handle, - ImageLayout = (uint)ImageLayout.TransferSrcOptimal, - ImageTiling = (uint)ImageTiling.Optimal, - ImageUsageFlags = (uint)(ImageUsageFlags.ImageUsageColorAttachmentBit - | ImageUsageFlags.ImageUsageTransferSrcBit - | ImageUsageFlags.ImageUsageTransferDstBit), - LevelCount = 1, - SampleCount = 1, - Protected = false, - Alloc = new GRVkAlloc() - { - Memory = image.Memory.Handle, - Flags = 0, - Offset = image.MemoryOffset, - Size = image.MemorySize - } - }; - - using var backendTexture = new GRBackendRenderTarget( - (int)image.Extent.Width, - (int)image.Extent.Height, - 1, - imageInfo); - - var vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>(); - - using var surface = SKSurface.Create( - skiaDrawingContextImpl.GrContext, - backendTexture, - GRSurfaceOrigin.TopLeft, - SKColorType.Rgba8888); - - if (surface == null) - { - return; - } - - var rect = new Rect(new Point(), new Size(image.Extent.Width, image.Extent.Height)); - - using var snapshot = surface.Snapshot(); - skiaDrawingContextImpl.SkCanvas.DrawImage(snapshot, rect.ToSKRect(), _control.Bounds.ToSKRect(), - new SKPaint()); - } - } - } -} diff --git a/Ryujinx.Ava/Ui/Controls/Win32NativeInterop.cs b/Ryujinx.Ava/Ui/Controls/Win32NativeInterop.cs new file mode 100644 index 00000000..124536d9 --- /dev/null +++ b/Ryujinx.Ava/Ui/Controls/Win32NativeInterop.cs @@ -0,0 +1,113 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Ava.Ui.Controls +{ + [SupportedOSPlatform("windows")] + internal class Win32NativeInterop + { + [Flags] + public enum ClassStyles : uint + { + CS_CLASSDC = 0x40, + CS_OWNDC = 0x20, + } + + [Flags] + public enum WindowStyles : uint + { + WS_CHILD = 0x40000000 + } + + public enum Cursors : uint + { + IDC_ARROW = 32512 + } + + public enum WindowsMessages : uint + { + MOUSEMOVE = 0x0200, + LBUTTONDOWN = 0x0201, + LBUTTONUP = 0x0202, + LBUTTONDBLCLK = 0x0203, + RBUTTONDOWN = 0x0204, + RBUTTONUP = 0x0205, + RBUTTONDBLCLK = 0x0206, + MBUTTONDOWN = 0x0207, + MBUTTONUP = 0x0208, + MBUTTONDBLCLK = 0x0209, + MOUSEWHEEL = 0x020A, + XBUTTONDOWN = 0x020B, + XBUTTONUP = 0x020C, + XBUTTONDBLCLK = 0x020D, + MOUSEHWHEEL = 0x020E, + MOUSELAST = 0x020E + } + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + internal delegate IntPtr WindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct WNDCLASSEX + { + public int cbSize; + public ClassStyles style; + [MarshalAs(UnmanagedType.FunctionPtr)] + public WindowProc lpfnWndProc; // not WndProc + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hbrBackground; + [MarshalAs(UnmanagedType.LPWStr)] + public string lpszMenuName; + [MarshalAs(UnmanagedType.LPWStr)] + public string lpszClassName; + public IntPtr hIconSm; + + public static WNDCLASSEX Create() + { + return new WNDCLASSEX + { + cbSize = Marshal.SizeOf<WNDCLASSEX>() + }; + } + } + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern ushort RegisterClassEx(ref WNDCLASSEX param); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern short UnregisterClass([MarshalAs(UnmanagedType.LPWStr)] string lpClassName, IntPtr instance); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + public static extern IntPtr DefWindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam); + + [DllImport("kernel32.dll")] + public static extern IntPtr GetModuleHandle(string lpModuleName); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DestroyWindow(IntPtr hwnd); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr CreateWindowEx( + uint dwExStyle, + string lpClassName, + string lpWindowName, + WindowStyles dwStyle, + int x, + int y, + int nWidth, + int nHeight, + IntPtr hWndParent, + IntPtr hMenu, + IntPtr hInstance, + IntPtr lpParam); + } +} |
