diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs index 5c4f5bd8b2..067be5c018 100644 --- a/Ryujinx.Ava/AppHost.cs +++ b/Ryujinx.Ava/AppHost.cs @@ -12,9 +12,9 @@ using Ryujinx.Audio.Integration; using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Input; -using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Renderer; using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; @@ -44,7 +44,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; @@ -66,8 +65,8 @@ namespace Ryujinx.Ava private const float VolumeDelta = 0.05f; private static readonly Cursor InvisibleCursor = new(StandardCursorType.None); - private readonly IntPtr InvisibleCursorWin; - private readonly IntPtr DefaultCursorWin; + private readonly IntPtr InvisibleCursorWin; + private readonly IntPtr DefaultCursorWin; private readonly long _ticksPerFrame; private readonly Stopwatch _chrono; @@ -80,6 +79,7 @@ namespace Ryujinx.Ava private readonly MainWindowViewModel _viewModel; private readonly IKeyboard _keyboardInterface; private readonly TopLevel _topLevel; + public RendererHost _rendererHost; private readonly GraphicsDebugLevel _glLogLevel; private float _newVolume; @@ -105,7 +105,6 @@ namespace Ryujinx.Ava public event EventHandler AppExit; public event EventHandler StatusUpdatedEvent; - public RendererHost Renderer { get; } public VirtualFileSystem VirtualFileSystem { get; } public ContentManager ContentManager { get; } public NpadManager NpadManager { get; } @@ -117,7 +116,6 @@ namespace Ryujinx.Ava public string ApplicationPath { get; private set; } public bool ScreenshotRequested { get; set; } - public AppHost( RendererHost renderer, InputManager inputManager, @@ -144,11 +142,12 @@ namespace Ryujinx.Ava NpadManager = _inputManager.CreateNpadManager(); TouchScreenManager = _inputManager.CreateTouchScreenManager(); - Renderer = renderer; ApplicationPath = applicationPath; VirtualFileSystem = virtualFileSystem; ContentManager = contentManager; + _rendererHost = renderer; + _chrono = new Stopwatch(); _ticksPerFrame = Stopwatch.Frequency / TargetFps; @@ -183,10 +182,10 @@ namespace Ryujinx.Ava { _lastCursorMoveTime = Stopwatch.GetTimestamp(); - if ((Renderer.Content as EmbeddedWindow).TransformedBounds != null) + if (_rendererHost.EmbeddedWindow.TransformedBounds != null) { var point = e.GetCurrentPoint(window).Position; - var bounds = (Renderer.Content as EmbeddedWindow).TransformedBounds.Value.Clip; + var bounds = _rendererHost.EmbeddedWindow.TransformedBounds.Value.Clip; _isCursorInRenderer = point.X >= bounds.X && point.X <= bounds.Width + bounds.X && @@ -318,7 +317,7 @@ namespace Ryujinx.Ava _viewModel.SetUIProgressHandlers(Device); - Renderer.SizeChanged += Window_SizeChanged; + _rendererHost.SizeChanged += Window_SizeChanged; _isActive = true; @@ -430,11 +429,11 @@ namespace Ryujinx.Ava _windowsMultimediaTimerResolution = null; } - Renderer?.MakeCurrent(); + (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(); Device.DisposeGpu(); - Renderer?.MakeCurrent(null); + (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null); } private void HideCursorState_Changed(object sender, ReactiveEventArgs state) @@ -635,11 +634,12 @@ namespace Ryujinx.Ava // Initialize Renderer. IRenderer renderer; - if (Renderer.IsVulkan) + if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) { - string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value; - - renderer = new VulkanRenderer(Renderer.CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu); + renderer = new VulkanRenderer( + (_rendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface, + VulkanHelper.GetRequiredInstanceExtensions, + ConfigurationState.Instance.Graphics.PreferredGpu.Value); } else { @@ -787,14 +787,12 @@ namespace Ryujinx.Ava _renderer.ScreenCaptured += Renderer_ScreenCaptured; - (_renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Renderer.GetContext())); - - Renderer.MakeCurrent(); + (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.InitializeBackgroundContext(_renderer); Device.Gpu.Renderer.Initialize(_glLogLevel); - Width = (int)Renderer.Bounds.Width; - Height = (int)Renderer.Bounds.Height; + Width = (int)_rendererHost.Bounds.Width; + Height = (int)_rendererHost.Bounds.Height; _renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling)); @@ -827,7 +825,7 @@ namespace Ryujinx.Ava _viewModel.SwitchToRenderer(false); } - Device.PresentFrame(() => Renderer?.SwapBuffers()); + Device.PresentFrame(() => (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers()); } if (_ticks >= _ticksPerFrame) @@ -837,7 +835,7 @@ namespace Ryujinx.Ava } }); - Renderer?.MakeCurrent(null); + (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null); } public void UpdateStatus() @@ -853,7 +851,7 @@ namespace Ryujinx.Ava StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( Device.EnableDeviceVsync, LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", - Renderer.IsVulkan ? "Vulkan" : "OpenGL", + ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan ? "Vulkan" : "OpenGL", dockedMode, ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", diff --git a/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs index bae4762ebe..2dd65e3629 100644 --- a/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs +++ b/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs @@ -136,7 +136,7 @@ namespace Ryujinx.Ava.UI.Applet Dispatcher.UIThread.Post(() => { _hiddenTextBox.Clear(); - _parent.ViewModel.RendererControl.Focus(); + _parent.ViewModel.RendererHostControl.Focus(); _parent = null; }); diff --git a/Ryujinx.Ava/UI/Controls/RendererHost.axaml.cs b/Ryujinx.Ava/UI/Controls/RendererHost.axaml.cs deleted file mode 100644 index 97058fa499..0000000000 --- a/Ryujinx.Ava/UI/Controls/RendererHost.axaml.cs +++ /dev/null @@ -1,127 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using Ryujinx.Ava.UI.Helpers; -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; - 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 RendererInitialized; - public event Action 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/Helpers/AvaloniaGlxContext.cs b/Ryujinx.Ava/UI/Helpers/AvaloniaGlxContext.cs deleted file mode 100644 index 6b696ba739..0000000000 --- a/Ryujinx.Ava/UI/Helpers/AvaloniaGlxContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -using SPB.Graphics; -using System; -using System.Runtime.Versioning; - -namespace Ryujinx.Ava.UI.Helpers -{ - [SupportedOSPlatform("linux")] - internal class AvaloniaGlxContext : SPB.Platform.GLX.GLXOpenGLContext - { - public AvaloniaGlxContext(IntPtr handle) - : base(FramebufferFormat.Default, 0, 0, 0, false, null) - { - ContextHandle = handle; - } - } -} diff --git a/Ryujinx.Ava/UI/Helpers/AvaloniaWglContext.cs b/Ryujinx.Ava/UI/Helpers/AvaloniaWglContext.cs deleted file mode 100644 index b63a973a11..0000000000 --- a/Ryujinx.Ava/UI/Helpers/AvaloniaWglContext.cs +++ /dev/null @@ -1,16 +0,0 @@ -using SPB.Graphics; -using System; -using System.Runtime.Versioning; - -namespace Ryujinx.Ava.UI.Helpers -{ - [SupportedOSPlatform("windows")] - internal class AvaloniaWglContext : SPB.Platform.WGL.WGLOpenGLContext - { - public AvaloniaWglContext(IntPtr handle) - : base(FramebufferFormat.Default, 0, 0, 0, false, null) - { - ContextHandle = handle; - } - } -} diff --git a/Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs b/Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs deleted file mode 100644 index 67ab80aa70..0000000000 --- a/Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs +++ /dev/null @@ -1,235 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Platform; -using SPB.Graphics; -using SPB.Platform; -using SPB.Platform.GLX; -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.Helpers -{ - 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; } - - private UpdateBoundsCallbackDelegate _updateBoundsCallback; - - public event EventHandler WindowCreated; - public event EventHandler SizeChanged; - - protected virtual void OnWindowDestroyed() { } - protected virtual void OnWindowDestroying() - { - WindowHandle = IntPtr.Zero; - X11Display = IntPtr.Zero; - NsView = IntPtr.Zero; - MetalLayer = IntPtr.Zero; - } - - public EmbeddedWindow() - { - var stateObserverable = this.GetObservable(BoundsProperty); - - stateObserverable.Subscribe(StateChanged); - - 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); - _updateBoundsCallback?.Invoke(rect); - } - - protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent) - { - if (OperatingSystem.IsLinux()) - { - return CreateLinux(parent); - } - else if (OperatingSystem.IsWindows()) - { - return CreateWin32(parent); - } - else if (OperatingSystem.IsMacOS()) - { - return CreateMacOs(parent); - } - - return base.CreateNativeControlCore(parent); - } - - 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")] - protected virtual 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")] - IPlatformHandle CreateWin32(IPlatformHandle parent) - { - _className = "NativeWindow-" + Guid.NewGuid(); - _wndProcDelegate = WndProc; - var wndClassEx = new WNDCLASSEX - { - cbSize = Marshal.SizeOf(), - hInstance = GetModuleHandle(null), - lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), - style = ClassStyles.CS_OWNDC, - lpszClassName = Marshal.StringToHGlobalUni(_className), - hCursor = CreateArrowCursor() - }; - - 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; - - Marshal.FreeHGlobal(wndClassEx.lpszClassName); - - return new PlatformHandle(WindowHandle, "HWND"); - } - - [SupportedOSPlatform("windows")] - 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 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 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 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, wParam, lParam); - } - - [SupportedOSPlatform("macos")] - IPlatformHandle CreateMacOs(IPlatformHandle parent) - { - MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback); - - NsView = nsView; - - return new PlatformHandle(nsView, "NSView"); - } - - void DestroyLinux() - { - X11Window?.Dispose(); - } - - [SupportedOSPlatform("windows")] - void DestroyWin32(IPlatformHandle handle) - { - DestroyWindow(handle.Handle); - UnregisterClass(_className, GetModuleHandle(null)); - } - - [SupportedOSPlatform("macos")] - void DestroyMacOS() - { - MetalHelper.DestroyMetalLayer(NsView, MetalLayer); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Helpers/IGlContextExtension.cs b/Ryujinx.Ava/UI/Helpers/IGlContextExtension.cs deleted file mode 100644 index e69774c307..0000000000 --- a/Ryujinx.Ava/UI/Helpers/IGlContextExtension.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Avalonia.OpenGL; -using SPB.Graphics.OpenGL; -using System; - -namespace Ryujinx.Ava.UI.Helpers -{ - internal static class IGlContextExtension - { - public static OpenGLContextBase AsOpenGLContextBase(this IGlContext context) - { - var handle = (IntPtr)context.GetType().GetProperty("Handle").GetValue(context); - - if (OperatingSystem.IsWindows()) - { - return new AvaloniaWglContext(handle); - } - else if (OperatingSystem.IsLinux()) - { - return new AvaloniaGlxContext(handle); - } - - return null; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Helpers/VulkanEmbeddedWindow.cs b/Ryujinx.Ava/UI/Helpers/VulkanEmbeddedWindow.cs deleted file mode 100644 index 6581610b6a..0000000000 --- a/Ryujinx.Ava/UI/Helpers/VulkanEmbeddedWindow.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Avalonia.Platform; -using Silk.NET.Vulkan; -using SPB.Graphics.Vulkan; -using SPB.Platform.GLX; -using SPB.Platform.Metal; -using SPB.Platform.Win32; -using SPB.Platform.X11; -using SPB.Windowing; -using System; -using System.Runtime.Versioning; - -namespace Ryujinx.Ava.UI.Helpers -{ - public class VulkanEmbeddedWindow : EmbeddedWindow - { - private NativeWindowBase _window; - - [SupportedOSPlatform("linux")] - protected override IPlatformHandle CreateLinux(IPlatformHandle parent) - { - X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(parent.Handle)); - WindowHandle = X11Window.WindowHandle.RawHandle; - X11Display = X11Window.DisplayHandle.RawHandle; - - X11Window.Hide(); - - return new PlatformHandle(WindowHandle, "X11"); - } - - public SurfaceKHR CreateSurface(Instance instance) - { - if (OperatingSystem.IsWindows()) - { - _window = new SimpleWin32Window(new NativeHandle(WindowHandle)); - } - else if (OperatingSystem.IsLinux()) - { - _window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle)); - } - else if (OperatingSystem.IsMacOS()) - { - _window = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer)); - } - else - { - throw new PlatformNotSupportedException(); - } - - return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, _window)); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs b/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs new file mode 100644 index 0000000000..21d9d12e2c --- /dev/null +++ b/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs @@ -0,0 +1,258 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Platform; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common.Configuration; +using Ryujinx.Ui.Common.Configuration; +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; } + + private UpdateBoundsCallbackDelegate _updateBoundsCallback; + + public event EventHandler WindowCreated; + public event EventHandler SizeChanged; + + 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) + { + SizeChanged?.Invoke(this, rect.Size); + _updateBoundsCallback?.Invoke(rect); + } + + protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle control) + { + if (OperatingSystem.IsLinux()) + { + return CreateLinux(control); + } + else if (OperatingSystem.IsWindows()) + { + return CreateWin32(control); + } + else 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")] + protected virtual IPlatformHandle CreateLinux(IPlatformHandle control) + { + if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) + { + X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(control.Handle)); + } + else + { + 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")] + IPlatformHandle CreateWin32(IPlatformHandle control) + { + _className = "NativeWindow-" + Guid.NewGuid(); + + _wndProcDelegate = delegate (IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam) + { + if (VisualRoot != null) + { + Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), VisualRoot).Value; + Pointer pointer = new(0, PointerType.Mouse, true); + + 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, + VisualRoot, + 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, + VisualRoot, + 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, + VisualRoot, + rootVisualPosition, + (ulong)Environment.TickCount64, + new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other), + KeyModifiers.None); + + RaiseEvent(evnt); + + break; + } + } + } + + return DefWindowProc(hWnd, msg, wParam, lParam); + }; + + WNDCLASSEX wndClassEx = new() + { + cbSize = Marshal.SizeOf(), + hInstance = GetModuleHandle(null), + lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), + style = ClassStyles.CS_OWNDC, + lpszClassName = Marshal.StringToHGlobalUni(_className), + hCursor = CreateArrowCursor() + }; + + RegisterClassEx(ref wndClassEx); + + WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WS_CHILD, 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() + { + MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback); + + 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")] + void DestroyMacOS() + { + MetalHelper.DestroyMetalLayer(NsView, MetalLayer); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Helpers/OpenGLEmbeddedWindow.cs b/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs similarity index 60% rename from Ryujinx.Ava/UI/Helpers/OpenGLEmbeddedWindow.cs rename to Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs index db77f66bf0..305e891a1e 100644 --- a/Ryujinx.Ava/UI/Helpers/OpenGLEmbeddedWindow.cs +++ b/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs @@ -1,5 +1,8 @@ using OpenTK.Graphics.OpenGL; using Ryujinx.Common.Configuration; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.Ui.Common.Configuration; using SPB.Graphics; using SPB.Graphics.OpenGL; using SPB.Platform; @@ -7,26 +10,20 @@ using SPB.Platform.WGL; using SPB.Windowing; using System; -namespace Ryujinx.Ava.UI.Helpers +namespace Ryujinx.Ava.UI.Renderer { - public class OpenGLEmbeddedWindow : EmbeddedWindow + public class EmbeddedWindowOpenGL : 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; - } + public EmbeddedWindowOpenGL() { } protected override void OnWindowDestroying() { Context.Dispose(); + base.OnWindowDestroying(); } @@ -48,19 +45,20 @@ namespace Ryujinx.Ava.UI.Helpers } var flags = OpenGLContextFlags.Compat; - if (_graphicsDebugLevel != GraphicsDebugLevel.None) + if (ConfigurationState.Instance.Logger.GraphicsDebugLevel != GraphicsDebugLevel.None) { flags |= OpenGLContextFlags.Debug; } - Context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, _major, _minor, flags); + 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); - var bindingsContext = new OpenToolkitBindingsContext(Context.GetProcAddress); + GL.LoadBindings(new OpenTKBindingsContext(Context.GetProcAddress)); - GL.LoadBindings(bindingsContext); Context.MakeCurrent(null); } @@ -76,7 +74,14 @@ namespace Ryujinx.Ava.UI.Helpers public void SwapBuffers() { - _window.SwapBuffers(); + _window?.SwapBuffers(); + } + + public void InitializeBackgroundContext(IRenderer renderer) + { + (renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Context)); + + MakeCurrent(); } } } \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs b/Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs new file mode 100644 index 0000000000..0b3eb9e30c --- /dev/null +++ b/Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs @@ -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 api) + { + return CreateSurface(instance); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Helpers/OpenToolkitBindingsContext.cs b/Ryujinx.Ava/UI/Renderer/OpenTKBindingsContext.cs similarity index 62% rename from Ryujinx.Ava/UI/Helpers/OpenToolkitBindingsContext.cs rename to Ryujinx.Ava/UI/Renderer/OpenTKBindingsContext.cs index efb703bab6..a2ec02b253 100644 --- a/Ryujinx.Ava/UI/Helpers/OpenToolkitBindingsContext.cs +++ b/Ryujinx.Ava/UI/Renderer/OpenTKBindingsContext.cs @@ -1,13 +1,13 @@ using OpenTK; using System; -namespace Ryujinx.Ava.UI.Helpers +namespace Ryujinx.Ava.UI.Renderer { - internal class OpenToolkitBindingsContext : IBindingsContext + internal class OpenTKBindingsContext : IBindingsContext { private readonly Func _getProcAddress; - public OpenToolkitBindingsContext(Func getProcAddress) + public OpenTKBindingsContext(Func getProcAddress) { _getProcAddress = getProcAddress; } diff --git a/Ryujinx.Ava/UI/Controls/RendererHost.axaml b/Ryujinx.Ava/UI/Renderer/RendererHost.axaml similarity index 84% rename from Ryujinx.Ava/UI/Controls/RendererHost.axaml rename to Ryujinx.Ava/UI/Renderer/RendererHost.axaml index 1cc557f066..bb96b10d2a 100644 --- a/Ryujinx.Ava/UI/Controls/RendererHost.axaml +++ b/Ryujinx.Ava/UI/Renderer/RendererHost.axaml @@ -6,6 +6,6 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Ryujinx.Ava.UI.Controls.RendererHost" + x:Class="Ryujinx.Ava.UI.Renderer.RendererHost" Focusable="True"> - + \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs b/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs new file mode 100644 index 0000000000..16a46df423 --- /dev/null +++ b/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs @@ -0,0 +1,69 @@ +using Avalonia; +using Avalonia.Controls; +using Ryujinx.Common.Configuration; +using Ryujinx.Ui.Common.Configuration; +using Silk.NET.Vulkan; +using System; + +namespace Ryujinx.Ava.UI.Renderer +{ + public partial class RendererHost : UserControl, IDisposable + { + public EmbeddedWindow EmbeddedWindow; + + public event EventHandler WindowCreated; + public event Action SizeChanged; + + public RendererHost() + { + InitializeComponent(); + + Dispose(); + + if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl) + { + EmbeddedWindow = new EmbeddedWindowOpenGL(); + } + else + { + EmbeddedWindow = new EmbeddedWindowVulkan(); + } + + Initialize(); + } + + private void Initialize() + { + EmbeddedWindow.WindowCreated += CurrentWindow_WindowCreated; + EmbeddedWindow.SizeChanged += CurrentWindow_SizeChanged; + + Content = EmbeddedWindow; + } + + public void Dispose() + { + if (EmbeddedWindow != null) + { + EmbeddedWindow.WindowCreated -= CurrentWindow_WindowCreated; + EmbeddedWindow.SizeChanged -= CurrentWindow_SizeChanged; + } + } + + 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) + { + WindowCreated?.Invoke(this, EventArgs.Empty); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/UI/Helpers/SPBOpenGLContext.cs b/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs similarity index 73% rename from Ryujinx.Ava/UI/Helpers/SPBOpenGLContext.cs rename to Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs index 21f206c83d..e090f14c7a 100644 --- a/Ryujinx.Ava/UI/Helpers/SPBOpenGLContext.cs +++ b/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs @@ -5,17 +5,17 @@ using SPB.Graphics.OpenGL; using SPB.Platform; using SPB.Windowing; -namespace Ryujinx.Ava.UI.Helpers +namespace Ryujinx.Ava.UI.Renderer { class SPBOpenGLContext : IOpenGLContext { - private OpenGLContextBase _context; - private NativeWindowBase _window; + private readonly OpenGLContextBase _context; + private readonly NativeWindowBase _window; private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window) { _context = context; - _window = window; + _window = window; } public void Dispose() @@ -32,12 +32,12 @@ namespace Ryujinx.Ava.UI.Helpers 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); + NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100); context.Initialize(window); context.MakeCurrent(window); - GL.LoadBindings(new OpenToolkitBindingsContext(context.GetProcAddress)); + GL.LoadBindings(new OpenTKBindingsContext(context.GetProcAddress)); context.MakeCurrent(null); diff --git a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs index 2954021553..a02b642959 100644 --- a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs +++ b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs @@ -13,6 +13,7 @@ using Ryujinx.Ava.Input; using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Renderer; using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Configuration; @@ -870,7 +871,7 @@ namespace Ryujinx.Ava.UI.ViewModels public Action SwitchToGameControl { get; private set; } public Action SetMainContent { get; private set; } public TopLevel TopLevel { get; private set; } - public RendererHost RendererControl { get; private set; } + public RendererHost RendererHostControl { get; private set; } public bool IsClosing { get; set; } public LibHacHorizonManager LibHacHorizonManager { get; internal set; } public IHostUiHandler UiHandler { get; internal set; } @@ -1144,7 +1145,7 @@ namespace Ryujinx.Ava.UI.ViewModels private void InitializeGame() { - RendererControl.RendererInitialized += GlRenderer_Created; + RendererHostControl.WindowCreated += RendererHost_Created; AppHost.StatusUpdatedEvent += Update_StatusBar; AppHost.AppExit += AppHost_AppExit; @@ -1203,7 +1204,7 @@ namespace Ryujinx.Ava.UI.ViewModels } } - private void GlRenderer_Created(object sender, EventArgs e) + private void RendererHost_Created(object sender, EventArgs e) { ShowLoading(false); @@ -1731,18 +1732,10 @@ namespace Ryujinx.Ava.UI.ViewModels PrepareLoadScreen(); - RendererControl = new RendererHost(ConfigurationState.Instance.Logger.GraphicsDebugLevel); - if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl) - { - RendererControl.CreateOpenGL(); - } - else - { - RendererControl.CreateVulkan(); - } + RendererHostControl = new RendererHost(); AppHost = new AppHost( - RendererControl, + RendererHostControl, InputManager, path, VirtualFileSystem, @@ -1783,9 +1776,9 @@ namespace Ryujinx.Ava.UI.ViewModels { SwitchToGameControl(startFullscreen); - SetMainContent(RendererControl); + SetMainContent(RendererHostControl); - RendererControl.Focus(); + RendererHostControl.Focus(); }); } @@ -1853,8 +1846,8 @@ namespace Ryujinx.Ava.UI.ViewModels HandleRelaunch(); }); - RendererControl.RendererInitialized -= GlRenderer_Created; - RendererControl = null; + RendererHostControl.WindowCreated -= RendererHost_Created; + RendererHostControl = null; SelectedIcon = null;