diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs index 7e3cddc8..7cf5934a 100644 --- a/Ryujinx.Ava/AppHost.cs +++ b/Ryujinx.Ava/AppHost.cs @@ -1,6 +1,5 @@ using ARMeilleure.Translation; using ARMeilleure.Translation.PTC; -using Avalonia; using Avalonia.Input; using Avalonia.Threading; using LibHac.Tools.FsSystem; @@ -12,10 +11,8 @@ using Ryujinx.Audio.Integration; using Ryujinx.Ava.Common; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Input; -using Ryujinx.Ava.Ui.Backend.Vulkan; using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Models; -using Ryujinx.Ava.Ui.Vulkan; using Ryujinx.Ava.Ui.Windows; using Ryujinx.Common; using Ryujinx.Common.Configuration; @@ -39,6 +36,7 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SPB.Graphics.Vulkan; using System; using System.Diagnostics; using System.IO; @@ -58,24 +56,24 @@ namespace Ryujinx.Ava { private const int CursorHideIdleTime = 8; // Hide Cursor seconds private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. + private const int TargetFps = 60; - private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None); + private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None); + private readonly long _ticksPerFrame; + private readonly Stopwatch _chrono; private readonly AccountManager _accountManager; private readonly UserChannelPersistence _userChannelPersistence; - private readonly InputManager _inputManager; - - private readonly IKeyboard _keyboardInterface; - private readonly MainWindow _parent; - + private readonly IKeyboard _keyboardInterface; private readonly GraphicsDebugLevel _glLogLevel; private bool _hideCursorOnIdle; private bool _isStopped; private bool _isActive; private long _lastCursorMoveTime; + private long _ticks = 0; private KeyboardHotkeyState _prevHotkeyState; @@ -93,7 +91,7 @@ namespace Ryujinx.Ava public event EventHandler AppExit; public event EventHandler StatusUpdatedEvent; - public RendererControl Renderer { get; } + public RendererHost Renderer { get; } public VirtualFileSystem VirtualFileSystem { get; } public ContentManager ContentManager { get; } public Switch Device { get; set; } @@ -111,7 +109,7 @@ namespace Ryujinx.Ava private object _lockObject = new(); public AppHost( - RendererControl renderer, + RendererHost renderer, InputManager inputManager, string applicationPath, VirtualFileSystem virtualFileSystem, @@ -128,7 +126,7 @@ namespace Ryujinx.Ava _hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle; _lastCursorMoveTime = Stopwatch.GetTimestamp(); _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; - _inputManager.SetMouseDriver(new AvaloniaMouseDriver(renderer)); + _inputManager.SetMouseDriver(new AvaloniaMouseDriver(_parent, renderer)); _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); NpadManager = _inputManager.CreateNpadManager(); @@ -138,6 +136,9 @@ namespace Ryujinx.Ava VirtualFileSystem = virtualFileSystem; ContentManager = contentManager; + _chrono = new Stopwatch(); + _ticksPerFrame = Stopwatch.Frequency / TargetFps; + if (ApplicationPath.StartsWith("@SystemContent")) { ApplicationPath = _parent.VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath); @@ -177,7 +178,7 @@ namespace Ryujinx.Ava if (_renderer != null) { double scale = _parent.PlatformImpl.RenderScaling; - _renderer.Window.SetSize((int)(size.Width * scale), (int)(size.Height * scale)); + _renderer.Window?.SetSize((int)(size.Width * scale), (int)(size.Height * scale)); } } @@ -335,8 +336,6 @@ namespace Ryujinx.Ava return; } - AvaloniaLocator.Current.GetService()?.MainSurface.Display.ChangeVSyncMode(true); - _isStopped = true; _isActive = false; } @@ -376,6 +375,8 @@ namespace Ryujinx.Ava _gpuCancellationTokenSource.Cancel(); _gpuCancellationTokenSource.Dispose(); + + _chrono.Stop(); } public void DisposeGpu() @@ -389,8 +390,7 @@ namespace Ryujinx.Ava Renderer?.MakeCurrent(); Device.DisposeGpu(); - - Renderer?.DestroyBackgroundContext(); + Renderer?.MakeCurrent(null); } @@ -596,16 +596,11 @@ namespace Ryujinx.Ava IRenderer renderer; - if (Program.UseVulkan) + if (Renderer.IsVulkan) { - var vulkan = AvaloniaLocator.Current.GetService(); + string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value; - renderer = new VulkanRenderer(vulkan.Instance.InternalHandle, - vulkan.MainSurface.Device.InternalHandle, - vulkan.PhysicalDevice.InternalHandle, - vulkan.MainSurface.Device.Queue.InternalHandle, - vulkan.PhysicalDevice.QueueFamilyIndex, - vulkan.MainSurface.Device.Lock); + renderer = new VulkanRenderer(Renderer.CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu); } else { @@ -778,11 +773,7 @@ namespace Ryujinx.Ava { Width = (int)e.Width; Height = (int)e.Height; - - if (!Program.UseVulkan) - { - SetRendererWindowSize(e); - } + SetRendererWindowSize(e); } private void MainLoop() @@ -822,12 +813,10 @@ namespace Ryujinx.Ava _renderer.ScreenCaptured += Renderer_ScreenCaptured; - (_renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext((Renderer as OpenGLRendererControl).GameContext)); + (_renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Renderer.GetContext())); Renderer.MakeCurrent(); - AvaloniaLocator.Current.GetService()?.MainSurface?.Display?.ChangeVSyncMode(Device.EnableDeviceVsync); - Device.Gpu.Renderer.Initialize(_glLogLevel); Width = (int)Renderer.Bounds.Width; @@ -835,16 +824,20 @@ namespace Ryujinx.Ava _renderer.Window.SetSize((int)(Width * _parent.PlatformImpl.RenderScaling), (int)(Height * _parent.PlatformImpl.RenderScaling)); + _chrono.Start(); + Device.Gpu.Renderer.RunLoop(() => { Device.Gpu.SetGpuThread(); Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); Translator.IsReadyForTranslation.Set(); - Renderer.Start(); - while (_isActive) { + _ticks += _chrono.ElapsedTicks; + + _chrono.Restart(); + if (Device.WaitFifo()) { Device.Statistics.RecordFifoStart(); @@ -860,19 +853,20 @@ namespace Ryujinx.Ava _parent.SwitchToGameControl(); } - Device.PresentFrame(Present); + Device.PresentFrame(() => Renderer?.SwapBuffers()); + } + + if (_ticks >= _ticksPerFrame) + { + UpdateStatus(); } } - - Renderer.Stop(); }); Renderer?.MakeCurrent(null); - - Renderer.SizeChanged -= Window_SizeChanged; } - private void Present(object image) + public void UpdateStatus() { // Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance["Docked"] : LocaleManager.Instance["Handheld"]; @@ -886,24 +880,12 @@ namespace Ryujinx.Ava StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( Device.EnableDeviceVsync, Device.GetVolume(), - Program.UseVulkan ? "Vulkan" : "OpenGL", + Renderer.IsVulkan ? "Vulkan" : "OpenGL", dockedMode, ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), LocaleManager.Instance["Game"] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %", $"GPU: {_renderer.GetHardwareInfo().GpuVendor}")); - - if (Program.UseVulkan) - { - var platformInterface = AvaloniaLocator.Current.GetService(); - if (platformInterface.MainSurface.Display.IsSurfaceChanged()) - { - SetRendererWindowSize(new Size(Width, Height)); - return; - } - } - - Renderer.Present(image); } public async Task ShowExitPrompt() @@ -985,8 +967,6 @@ namespace Ryujinx.Ava case KeyboardHotkeyState.ToggleVSync: Device.EnableDeviceVsync = !Device.EnableDeviceVsync; - AvaloniaLocator.Current.GetService()?.MainSurface?.Display?.ChangeVSyncMode(Device.EnableDeviceVsync); - break; case KeyboardHotkeyState.Screenshot: ScreenshotRequested = true; diff --git a/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs b/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs index 74c435b5..9ad0310a 100644 --- a/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs +++ b/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs @@ -14,20 +14,27 @@ namespace Ryujinx.Ava.Input private Control _widget; private bool _isDisposed; private Size _size; + private readonly Window _window; public bool[] PressedButtons { get; } public Vector2 CurrentPosition { get; private set; } public Vector2 Scroll { get; private set; } - public AvaloniaMouseDriver(Control parent) + public AvaloniaMouseDriver(Window window, Control parent) { _widget = parent; + _window = window; _widget.PointerMoved += Parent_PointerMovedEvent; _widget.PointerPressed += Parent_PointerPressEvent; _widget.PointerReleased += Parent_PointerReleaseEvent; _widget.PointerWheelChanged += Parent_ScrollEvent; + + _window.PointerMoved += Parent_PointerMovedEvent; + _window.PointerPressed += Parent_PointerPressEvent; + _window.PointerReleased += Parent_PointerReleaseEvent; + _window.PointerWheelChanged += Parent_ScrollEvent; PressedButtons = new bool[(int)MouseButton.Count]; @@ -47,7 +54,6 @@ namespace Ryujinx.Ava.Input private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args) { - var pointerProperties = args.GetCurrentPoint(_widget).Properties; PressedButtons[(int)args.InitialPressMouseButton - 1] = false; } @@ -125,6 +131,11 @@ namespace Ryujinx.Ava.Input _widget.PointerReleased -= Parent_PointerReleaseEvent; _widget.PointerWheelChanged -= Parent_ScrollEvent; + _window.PointerMoved -= Parent_PointerMovedEvent; + _window.PointerPressed -= Parent_PointerPressEvent; + _window.PointerReleased -= Parent_PointerReleaseEvent; + _window.PointerWheelChanged -= Parent_ScrollEvent; + _widget = null; } } diff --git a/Ryujinx.Ava/Program.cs b/Ryujinx.Ava/Program.cs index 242246eb..61b184c6 100644 --- a/Ryujinx.Ava/Program.cs +++ b/Ryujinx.Ava/Program.cs @@ -1,9 +1,7 @@ using ARMeilleure.Translation.PTC; using Avalonia; -using Avalonia.OpenGL; using Avalonia.Rendering; using Avalonia.Threading; -using Ryujinx.Ava.Ui.Backend; using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Windows; using Ryujinx.Common; @@ -12,12 +10,10 @@ using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.Logging; using Ryujinx.Common.System; using Ryujinx.Common.SystemInfo; -using Ryujinx.Graphics.Vulkan; using Ryujinx.Modules; using Ryujinx.Ui.Common; using Ryujinx.Ui.Common.Configuration; using System; -using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -34,7 +30,6 @@ namespace Ryujinx.Ava public static bool PreviewerDetached { get; private set; } public static RenderTimer RenderTimer { get; private set; } - public static bool UseVulkan { get; private set; } [DllImport("user32.dll", SetLastError = true)] public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type); @@ -71,36 +66,16 @@ namespace Ryujinx.Ava EnableMultiTouch = true, EnableIme = true, UseEGL = false, - UseGpu = !UseVulkan, - GlProfiles = new List() - { - new GlVersion(GlProfileType.OpenGL, 4, 3) - } + UseGpu = false }) .With(new Win32PlatformOptions { EnableMultitouch = true, - UseWgl = !UseVulkan, - WglProfiles = new List() - { - new GlVersion(GlProfileType.OpenGL, 4, 3) - }, + UseWgl = false, AllowEglInitialization = false, CompositionBackdropCornerRadius = 8f, }) .UseSkia() - .With(new Ui.Vulkan.VulkanOptions() - { - ApplicationName = "Ryujinx.Graphics.Vulkan", - MaxQueueCount = 2, - PreferDiscreteGpu = true, - PreferredDevice = !PreviewerDetached ? "" : ConfigurationState.Instance.Graphics.PreferredGpu.Value, - UseDebug = !PreviewerDetached ? false : ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value != GraphicsDebugLevel.None, - }) - .With(new SkiaOptions() - { - CustomGpuFactory = UseVulkan ? SkiaGpuFactory.CreateVulkanGpu : null - }) .AfterSetup(_ => { AvaloniaLocator.CurrentMutable @@ -176,26 +151,7 @@ namespace Ryujinx.Ava ReloadConfig(); - UseVulkan = PreviewerDetached ? ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan : false; - - if (UseVulkan) - { - if (VulkanRenderer.GetPhysicalDevices().Length == 0) - { - UseVulkan = false; - - ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl; - - Logger.Warning?.PrintMsg(LogClass.Application, "A suitable Vulkan physical device is not available. Falling back to OpenGL"); - } - } - - if (UseVulkan) - { - // With a custom gpu backend, avalonia doesn't enable dpi awareness, so the backend must handle it. This isn't so for the opengl backed, - // as that uses avalonia's gpu backend and it's enabled there. - ForceDpiAware.Windows(); - } + ForceDpiAware.Windows(); WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor(); ActualScaleFactor = ForceDpiAware.GetActualScaleFactor() / BaseDpi; diff --git a/Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs b/Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs index 564ee4b2..e4ddba96 100644 --- a/Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs +++ b/Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs @@ -1,3 +1,4 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; @@ -59,29 +60,63 @@ namespace Ryujinx.Ava.Ui.Controls string input = string.Empty; + var overlay = new ContentDialogOverlayWindow() + { + Height = window.Bounds.Height, + Width = window.Bounds.Width, + Position = window.PointToScreen(new Point()) + }; + + window.PositionChanged += OverlayOnPositionChanged; + + void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) + { + overlay.Position = window.PointToScreen(new Point()); + } + + contentDialog = overlay.ContentDialog; + + bool opened = false; + content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); - if (contentDialog != null) + content._host = contentDialog; + contentDialog.Title = title; + contentDialog.PrimaryButtonText = args.SubmitText; + contentDialog.IsPrimaryButtonEnabled = content._checkLength(content.Message.Length); + contentDialog.SecondaryButtonText = ""; + contentDialog.CloseButtonText = LocaleManager.Instance["InputDialogCancel"]; + contentDialog.Content = content; + + TypedEventHandler handler = (sender, eventArgs) => { - content._host = contentDialog; - contentDialog.Title = title; - contentDialog.PrimaryButtonText = args.SubmitText; - contentDialog.IsPrimaryButtonEnabled = content._checkLength(content.Message.Length); - contentDialog.SecondaryButtonText = ""; - contentDialog.CloseButtonText = LocaleManager.Instance["InputDialogCancel"]; - contentDialog.Content = content; - TypedEventHandler handler = (sender, eventArgs) => + if (eventArgs.Result == ContentDialogResult.Primary) { - if (eventArgs.Result == ContentDialogResult.Primary) - { - result = UserResult.Ok; - input = content.Input.Text; - } - }; - contentDialog.Closed += handler; + result = UserResult.Ok; + input = content.Input.Text; + } + }; + contentDialog.Closed += handler; + + overlay.Opened += OverlayOnActivated; + + async void OverlayOnActivated(object sender, EventArgs e) + { + if (opened) + { + return; + } + + opened = true; + + overlay.Position = window.PointToScreen(new Point()); + await contentDialog.ShowAsync(); contentDialog.Closed -= handler; - } + overlay.Close(); + }; + + await overlay.ShowDialog(window); return (result, input); } diff --git a/Ryujinx.Ava/Ui/Backend/BackendSurface.cs b/Ryujinx.Ava/Ui/Backend/BackendSurface.cs deleted file mode 100644 index 423fe038..00000000 --- a/Ryujinx.Ava/Ui/Backend/BackendSurface.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Avalonia; -using System; -using System.Runtime.InteropServices; -using static Ryujinx.Ava.Ui.Backend.Interop; - -namespace Ryujinx.Ava.Ui.Backend -{ - public abstract class BackendSurface : IDisposable - { - protected IntPtr Display => _display; - - private IntPtr _display = IntPtr.Zero; - - [DllImport("libX11.so.6")] - public static extern IntPtr XOpenDisplay(IntPtr display); - - [DllImport("libX11.so.6")] - public static extern int XCloseDisplay(IntPtr display); - - private PixelSize _currentSize; - public IntPtr Handle { get; protected set; } - - public bool IsDisposed { get; private set; } - - public BackendSurface(IntPtr handle) - { - Handle = handle; - - if (OperatingSystem.IsLinux()) - { - _display = XOpenDisplay(IntPtr.Zero); - } - } - - public PixelSize Size - { - get - { - PixelSize size = new PixelSize(); - if (OperatingSystem.IsWindows()) - { - GetClientRect(Handle, out var rect); - size = new PixelSize(rect.right, rect.bottom); - } - else if (OperatingSystem.IsLinux()) - { - XWindowAttributes attributes = new XWindowAttributes(); - XGetWindowAttributes(Display, Handle, ref attributes); - - size = new PixelSize(attributes.width, attributes.height); - } - - _currentSize = size; - - return size; - } - } - - public PixelSize CurrentSize => _currentSize; - - public virtual void Dispose() - { - if (IsDisposed) - { - throw new ObjectDisposedException(nameof(BackendSurface)); - } - - IsDisposed = true; - - if (_display != IntPtr.Zero) - { - XCloseDisplay(_display); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Backend/Interop.cs b/Ryujinx.Ava/Ui/Backend/Interop.cs deleted file mode 100644 index 617e9767..00000000 --- a/Ryujinx.Ava/Ui/Backend/Interop.cs +++ /dev/null @@ -1,49 +0,0 @@ -using FluentAvalonia.Interop; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.Ava.Ui.Backend -{ - public static class Interop - { - [StructLayout(LayoutKind.Sequential)] - public struct XWindowAttributes - { - public int x; - public int y; - public int width; - public int height; - public int border_width; - public int depth; - public IntPtr visual; - public IntPtr root; - public int c_class; - public int bit_gravity; - public int win_gravity; - public int backing_store; - public IntPtr backing_planes; - public IntPtr backing_pixel; - public int save_under; - public IntPtr colormap; - public int map_installed; - public int map_state; - public IntPtr all_event_masks; - public IntPtr your_event_mask; - public IntPtr do_not_propagate_mask; - public int override_direct; - public IntPtr screen; - } - - [DllImport("user32.dll")] - public static extern bool GetClientRect(IntPtr hwnd, out RECT lpRect); - - [DllImport("libX11.so.6")] - public static extern int XCloseDisplay(IntPtr display); - - [DllImport("libX11.so.6")] - public static extern int XGetWindowAttributes(IntPtr display, IntPtr window, ref XWindowAttributes attributes); - - [DllImport("libX11.so.6")] - public static extern IntPtr XOpenDisplay(IntPtr display); - } -} diff --git a/Ryujinx.Ava/Ui/Backend/SkiaGpuFactory.cs b/Ryujinx.Ava/Ui/Backend/SkiaGpuFactory.cs deleted file mode 100644 index 335bc905..00000000 --- a/Ryujinx.Ava/Ui/Backend/SkiaGpuFactory.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Avalonia; -using Avalonia.Skia; -using Ryujinx.Ava.Ui.Vulkan; -using Ryujinx.Ava.Ui.Backend.Vulkan; - -namespace Ryujinx.Ava.Ui.Backend -{ - public static class SkiaGpuFactory - { - public static ISkiaGpu CreateVulkanGpu() - { - var skiaOptions = AvaloniaLocator.Current.GetService() ?? new SkiaOptions(); - var platformInterface = AvaloniaLocator.Current.GetService(); - - if (platformInterface == null) - { - VulkanPlatformInterface.TryInitialize(); - } - - var gpu = new VulkanSkiaGpu(skiaOptions.MaxGpuResourceSizeBytes); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(gpu); - - return gpu; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs deleted file mode 100644 index 2a1cd229..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Silk.NET.Vulkan; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - public static class ResultExtensions - { - public static void ThrowOnError(this Result result) - { - // Only negative result codes are errors. - if ((int)result < (int)Result.Success) - { - throw new Exception($"Unexpected API error \"{result}\"."); - } - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs deleted file mode 100644 index 70ec39c7..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System; -using Avalonia; -using Avalonia.Skia; -using Ryujinx.Ava.Ui.Vulkan; -using Ryujinx.Ava.Ui.Vulkan.Surfaces; -using Silk.NET.Vulkan; -using SkiaSharp; - -namespace Ryujinx.Ava.Ui.Backend.Vulkan -{ - internal class VulkanRenderTarget : ISkiaGpuRenderTarget - { - public GRContext GrContext { get; private set; } - - private readonly VulkanSurfaceRenderTarget _surface; - private readonly VulkanPlatformInterface _vulkanPlatformInterface; - private readonly IVulkanPlatformSurface _vulkanPlatformSurface; - private GRVkBackendContext _grVkBackend; - - public VulkanRenderTarget(VulkanPlatformInterface vulkanPlatformInterface, IVulkanPlatformSurface vulkanPlatformSurface) - { - _surface = vulkanPlatformInterface.CreateRenderTarget(vulkanPlatformSurface); - _vulkanPlatformInterface = vulkanPlatformInterface; - _vulkanPlatformSurface = vulkanPlatformSurface; - - Initialize(); - } - - private void Initialize() - { - GRVkGetProcedureAddressDelegate getProc = GetVulkanProcAddress; - - _grVkBackend = new GRVkBackendContext() - { - VkInstance = _surface.Device.Handle, - VkPhysicalDevice = _vulkanPlatformInterface.PhysicalDevice.Handle, - VkDevice = _surface.Device.Handle, - VkQueue = _surface.Device.Queue.Handle, - GraphicsQueueIndex = _vulkanPlatformInterface.PhysicalDevice.QueueFamilyIndex, - GetProcedureAddress = getProc - }; - - GrContext = GRContext.CreateVulkan(_grVkBackend); - - var gpu = AvaloniaLocator.Current.GetService(); - - if (gpu.MaxResourceBytes.HasValue) - { - GrContext.SetResourceCacheLimit(gpu.MaxResourceBytes.Value); - } - } - - private IntPtr GetVulkanProcAddress(string name, IntPtr instanceHandle, IntPtr deviceHandle) - { - IntPtr addr; - - if (deviceHandle != IntPtr.Zero) - { - addr = _vulkanPlatformInterface.Api.GetDeviceProcAddr(new Device(deviceHandle), name); - - if (addr != IntPtr.Zero) - { - return addr; - } - - addr = _vulkanPlatformInterface.Api.GetDeviceProcAddr(new Device(_surface.Device.Handle), name); - - if (addr != IntPtr.Zero) - { - return addr; - } - } - - addr = _vulkanPlatformInterface.Api.GetInstanceProcAddr(new Instance(_vulkanPlatformInterface.Instance.Handle), name); - - if (addr == IntPtr.Zero) - { - addr = _vulkanPlatformInterface.Api.GetInstanceProcAddr(new Instance(instanceHandle), name); - } - - return addr; - } - - public void Dispose() - { - _grVkBackend.Dispose(); - GrContext.Dispose(); - _surface.Dispose(); - } - - public ISkiaGpuRenderSession BeginRenderingSession() - { - var session = _surface.BeginDraw(_vulkanPlatformSurface.Scaling); - bool success = false; - try - { - var disp = session.Display; - var api = session.Api; - - var size = session.Size; - var scaling = session.Scaling; - if (size.Width <= 0 || size.Height <= 0 || scaling < 0) - { - size = new Avalonia.PixelSize(1, 1); - scaling = 1; - } - - lock (GrContext) - { - GrContext.ResetContext(); - - var image = _surface.GetImage(); - - var imageInfo = new GRVkImageInfo() - { - CurrentQueueFamily = disp.QueueFamilyIndex, - Format = (uint)image.Format, - Image = image.Handle, - ImageLayout = (uint)image.CurrentLayout, - ImageTiling = (uint)image.Tiling, - ImageUsageFlags = _surface.UsageFlags, - LevelCount = _surface.MipLevels, - SampleCount = 1, - Protected = false, - Alloc = new GRVkAlloc() - { - Memory = image.MemoryHandle, - Flags = 0, - Offset = 0, - Size = _surface.MemorySize - } - }; - - var renderTarget = - new GRBackendRenderTarget((int)size.Width, (int)size.Height, 1, - imageInfo); - var surface = SKSurface.Create(GrContext, renderTarget, - GRSurfaceOrigin.TopLeft, - _surface.IsRgba ? SKColorType.Rgba8888 : SKColorType.Bgra8888, SKColorSpace.CreateSrgb()); - - if (surface == null) - { - throw new InvalidOperationException( - "Surface can't be created with the provided render target"); - } - - success = true; - - return new VulkanGpuSession(GrContext, renderTarget, surface, session); - } - } - finally - { - if (!success) - { - session.Dispose(); - } - } - } - - public bool IsCorrupted { get; } - - internal class VulkanGpuSession : ISkiaGpuRenderSession - { - private readonly GRBackendRenderTarget _backendRenderTarget; - private readonly VulkanSurfaceRenderingSession _vulkanSession; - - public VulkanGpuSession(GRContext grContext, - GRBackendRenderTarget backendRenderTarget, - SKSurface surface, - VulkanSurfaceRenderingSession vulkanSession) - { - GrContext = grContext; - _backendRenderTarget = backendRenderTarget; - SkSurface = surface; - _vulkanSession = vulkanSession; - - SurfaceOrigin = GRSurfaceOrigin.TopLeft; - } - - public void Dispose() - { - lock (_vulkanSession.Display.Lock) - { - SkSurface.Canvas.Flush(); - - SkSurface.Dispose(); - _backendRenderTarget.Dispose(); - GrContext.Flush(); - - _vulkanSession.Dispose(); - } - } - - public GRContext GrContext { get; } - public SKSurface SkSurface { get; } - public double ScaleFactor => _vulkanSession.Scaling; - public GRSurfaceOrigin SurfaceOrigin { get; } - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs deleted file mode 100644 index a5c27086..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using Avalonia; -using Avalonia.Platform; -using Avalonia.Skia; -using Avalonia.X11; -using Ryujinx.Ava.Ui.Vulkan; -using Silk.NET.Vulkan; -using SkiaSharp; - -namespace Ryujinx.Ava.Ui.Backend.Vulkan -{ - public class VulkanSkiaGpu : ISkiaGpu - { - private readonly VulkanPlatformInterface _vulkan; - public long? MaxResourceBytes { get; } - - public VulkanSkiaGpu(long? maxResourceBytes) - { - _vulkan = AvaloniaLocator.Current.GetService(); - MaxResourceBytes = maxResourceBytes; - } - - public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable surfaces) - { - foreach (var surface in surfaces) - { - VulkanWindowSurface window; - - if (surface is IPlatformHandle handle) - { - window = new VulkanWindowSurface(handle.Handle); - } - else if (surface is X11FramebufferSurface x11FramebufferSurface) - { - // As of Avalonia 0.10.13, an IPlatformHandle isn't passed for linux, so use reflection to otherwise get the window id - var xId = (IntPtr)x11FramebufferSurface.GetType().GetField( - "_xid", - System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(x11FramebufferSurface); - - window = new VulkanWindowSurface(xId); - } - else - { - continue; - } - - VulkanRenderTarget vulkanRenderTarget = new VulkanRenderTarget(_vulkan, window); - - return vulkanRenderTarget; - } - - return null; - } - - public ISkiaSurface TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session) - { - return null; - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSurface.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSurface.cs deleted file mode 100644 index fd2d379b..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSurface.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Avalonia; -using Ryujinx.Ava.Ui.Vulkan; -using Ryujinx.Ava.Ui.Vulkan.Surfaces; -using Silk.NET.Vulkan; -using Silk.NET.Vulkan.Extensions.KHR; -using System; - -namespace Ryujinx.Ava.Ui.Backend.Vulkan -{ - internal class VulkanWindowSurface : BackendSurface, IVulkanPlatformSurface - { - public float Scaling => (float)Program.ActualScaleFactor; - - public PixelSize SurfaceSize => Size; - - public VulkanWindowSurface(IntPtr handle) : base(handle) - { - } - - public unsafe SurfaceKHR CreateSurface(VulkanInstance instance) - { - if (OperatingSystem.IsWindows()) - { - if (instance.Api.TryGetInstanceExtension(new Instance(instance.Handle), out KhrWin32Surface surfaceExtension)) - { - var createInfo = new Win32SurfaceCreateInfoKHR() { Hinstance = 0, Hwnd = Handle, SType = StructureType.Win32SurfaceCreateInfoKhr }; - - surfaceExtension.CreateWin32Surface(new Instance(instance.Handle), createInfo, null, out var surface).ThrowOnError(); - - return surface; - } - } - else if (OperatingSystem.IsLinux()) - { - if (instance.Api.TryGetInstanceExtension(new Instance(instance.Handle), out KhrXlibSurface surfaceExtension)) - { - var createInfo = new XlibSurfaceCreateInfoKHR() - { - SType = StructureType.XlibSurfaceCreateInfoKhr, - Dpy = (nint*)Display, - Window = Handle - }; - - surfaceExtension.CreateXlibSurface(new Instance(instance.Handle), createInfo, null, out var surface).ThrowOnError(); - - return surface; - } - } - - throw new PlatformNotSupportedException("The current platform does not support surface creation."); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/Surfaces/IVulkanPlatformSurface.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/Surfaces/IVulkanPlatformSurface.cs deleted file mode 100644 index 642d8a6a..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/Surfaces/IVulkanPlatformSurface.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Avalonia; -using Silk.NET.Vulkan; - -namespace Ryujinx.Ava.Ui.Vulkan.Surfaces -{ - public interface IVulkanPlatformSurface : IDisposable - { - float Scaling { get; } - PixelSize SurfaceSize { get; } - SurfaceKHR CreateSurface(VulkanInstance instance); - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/Surfaces/VulkanSurfaceRenderTarget.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/Surfaces/VulkanSurfaceRenderTarget.cs deleted file mode 100644 index 510e6724..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/Surfaces/VulkanSurfaceRenderTarget.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using Avalonia; -using Ryujinx.Graphics.Vulkan; -using Silk.NET.Vulkan; - -namespace Ryujinx.Ava.Ui.Vulkan.Surfaces -{ - internal class VulkanSurfaceRenderTarget : IDisposable - { - private readonly VulkanPlatformInterface _platformInterface; - private readonly Format _format; - - private VulkanCommandBufferPool.VulkanCommandBuffer _commandBuffer; - private VulkanImage Image { get; set; } - private object _lock = new object(); - - public uint MipLevels => Image.MipLevels; - public VulkanDevice Device { get; } - - public VulkanSurfaceRenderTarget(VulkanPlatformInterface platformInterface, VulkanSurface surface) - { - _platformInterface = platformInterface; - - var device = VulkanInitialization.CreateDevice(platformInterface.Api, - platformInterface.PhysicalDevice.InternalHandle, - platformInterface.PhysicalDevice.QueueFamilyIndex, - VulkanInitialization.GetSupportedExtensions(platformInterface.Api, platformInterface.PhysicalDevice.InternalHandle), - platformInterface.PhysicalDevice.QueueCount); - - Device = new VulkanDevice(device, platformInterface.PhysicalDevice, platformInterface.Api); - - Display = VulkanDisplay.CreateDisplay( - platformInterface.Instance, - Device, - platformInterface.PhysicalDevice, - surface); - Surface = surface; - - // Skia seems to only create surfaces from images with unorm format - IsRgba = Display.SurfaceFormat.Format >= Format.R8G8B8A8Unorm && - Display.SurfaceFormat.Format <= Format.R8G8B8A8Srgb; - - _format = IsRgba ? Format.R8G8B8A8Unorm : Format.B8G8R8A8Unorm; - } - - public bool IsRgba { get; } - - public uint ImageFormat => (uint)_format; - - public ulong MemorySize => Image.MemorySize; - - public VulkanDisplay Display { get; private set; } - - public VulkanSurface Surface { get; private set; } - - public uint UsageFlags => Image.UsageFlags; - - public PixelSize Size { get; private set; } - - public void Dispose() - { - lock (_lock) - { - DestroyImage(); - Display?.Dispose(); - Surface?.Dispose(); - Device?.Dispose(); - - Display = null; - Surface = null; - } - } - - public VulkanSurfaceRenderingSession BeginDraw(float scaling) - { - if (Image == null) - { - RecreateImage(); - } - - _commandBuffer?.WaitForFence(); - _commandBuffer = null; - - var session = new VulkanSurfaceRenderingSession(Display, Device, this, scaling); - - Image.TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr); - - return session; - } - - public void RecreateImage() - { - DestroyImage(); - CreateImage(); - } - - private void CreateImage() - { - Size = Display.Size; - - Image = new VulkanImage(Device, _platformInterface.PhysicalDevice, Display.CommandBufferPool, ImageFormat, Size); - } - - private void DestroyImage() - { - _commandBuffer?.WaitForFence(); - _commandBuffer = null; - Image?.Dispose(); - Image = null; - } - - public VulkanImage GetImage() - { - return Image; - } - - public void EndDraw() - { - lock (_lock) - { - if (Display == null) - { - return; - } - - _commandBuffer = Display.StartPresentation(); - - Display.BlitImageToCurrentImage(this, _commandBuffer.InternalHandle); - - Display.EndPresentation(_commandBuffer); - } - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs deleted file mode 100644 index a00ecf2b..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; -using Silk.NET.Vulkan; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - internal class VulkanCommandBufferPool : IDisposable - { - private readonly VulkanDevice _device; - private readonly CommandPool _commandPool; - - private readonly List _usedCommandBuffers = new(); - private readonly object _lock = new object(); - - public unsafe VulkanCommandBufferPool(VulkanDevice device, VulkanPhysicalDevice physicalDevice) - { - _device = device; - - var commandPoolCreateInfo = new CommandPoolCreateInfo - { - SType = StructureType.CommandPoolCreateInfo, - Flags = CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit, - QueueFamilyIndex = physicalDevice.QueueFamilyIndex - }; - - device.Api.CreateCommandPool(_device.InternalHandle, commandPoolCreateInfo, null, out _commandPool) - .ThrowOnError(); - } - - private CommandBuffer AllocateCommandBuffer() - { - var commandBufferAllocateInfo = new CommandBufferAllocateInfo - { - SType = StructureType.CommandBufferAllocateInfo, - CommandPool = _commandPool, - CommandBufferCount = 1, - Level = CommandBufferLevel.Primary - }; - - lock (_lock) - { - _device.Api.AllocateCommandBuffers(_device.InternalHandle, commandBufferAllocateInfo, out var commandBuffer); - - return commandBuffer; - } - } - - public VulkanCommandBuffer CreateCommandBuffer() - { - return new(_device, this); - } - - public void FreeUsedCommandBuffers() - { - lock (_lock) - { - foreach (var usedCommandBuffer in _usedCommandBuffers) - { - usedCommandBuffer.Dispose(); - } - - _usedCommandBuffers.Clear(); - } - } - - private void DisposeCommandBuffer(VulkanCommandBuffer commandBuffer) - { - lock (_lock) - { - _usedCommandBuffers.Add(commandBuffer); - } - } - - public void Dispose() - { - lock (_lock) - { - FreeUsedCommandBuffers(); - _device.Api.DestroyCommandPool(_device.InternalHandle, _commandPool, Span.Empty); - } - } - - public class VulkanCommandBuffer : IDisposable - { - private readonly VulkanCommandBufferPool _commandBufferPool; - private readonly VulkanDevice _device; - private readonly Fence _fence; - private bool _hasEnded; - private bool _hasStarted; - private bool _isDisposed; - private object _lock = new object(); - - public IntPtr Handle => InternalHandle.Handle; - - internal CommandBuffer InternalHandle { get; } - - internal unsafe VulkanCommandBuffer(VulkanDevice device, VulkanCommandBufferPool commandBufferPool) - { - _device = device; - _commandBufferPool = commandBufferPool; - - InternalHandle = _commandBufferPool.AllocateCommandBuffer(); - - var fenceCreateInfo = new FenceCreateInfo() - { - SType = StructureType.FenceCreateInfo, - Flags = FenceCreateFlags.FenceCreateSignaledBit - }; - - device.Api.CreateFence(device.InternalHandle, fenceCreateInfo, null, out _fence); - } - - public void WaitForFence() - { - if (_isDisposed) - { - return; - } - - lock (_lock) - { - if (!_isDisposed) - { - _device.Api.WaitForFences(_device.InternalHandle, 1, _fence, true, ulong.MaxValue); - } - } - } - - public void BeginRecording() - { - if (!_hasStarted) - { - _hasStarted = true; - - var beginInfo = new CommandBufferBeginInfo - { - SType = StructureType.CommandBufferBeginInfo, - Flags = CommandBufferUsageFlags.CommandBufferUsageOneTimeSubmitBit - }; - - _device.Api.BeginCommandBuffer(InternalHandle, beginInfo); - } - } - - public void EndRecording() - { - if (_hasStarted && !_hasEnded) - { - _hasEnded = true; - - _device.Api.EndCommandBuffer(InternalHandle); - } - } - - public void Submit() - { - Submit(null, null, null, _fence); - } - - public unsafe void Submit( - ReadOnlySpan waitSemaphores, - ReadOnlySpan waitDstStageMask, - ReadOnlySpan signalSemaphores, - Fence? fence = null) - { - EndRecording(); - - if (!fence.HasValue) - { - fence = _fence; - } - - fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores) - { - fixed (PipelineStageFlags* pWaitDstStageMask = waitDstStageMask) - { - var commandBuffer = InternalHandle; - var submitInfo = new SubmitInfo - { - SType = StructureType.SubmitInfo, - WaitSemaphoreCount = waitSemaphores != null ? (uint)waitSemaphores.Length : 0, - PWaitSemaphores = pWaitSemaphores, - PWaitDstStageMask = pWaitDstStageMask, - CommandBufferCount = 1, - PCommandBuffers = &commandBuffer, - SignalSemaphoreCount = signalSemaphores != null ? (uint)signalSemaphores.Length : 0, - PSignalSemaphores = pSignalSemaphores, - }; - - _device.Api.ResetFences(_device.InternalHandle, 1, fence.Value); - - _device.Submit(submitInfo, fence.Value); - } - } - - _commandBufferPool.DisposeCommandBuffer(this); - } - - public void Dispose() - { - lock (_lock) - { - if (!_isDisposed) - { - _isDisposed = true; - - _device.Api.WaitForFences(_device.InternalHandle, 1, _fence, true, ulong.MaxValue); - _device.Api.FreeCommandBuffers(_device.InternalHandle, _commandBufferPool._commandPool, 1, InternalHandle); - _device.Api.DestroyFence(_device.InternalHandle, _fence, Span.Empty); - } - } - } - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs deleted file mode 100644 index 3d893e19..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using Silk.NET.Vulkan; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - internal class VulkanDevice : IDisposable - { - private static object _lock = new object(); - - public VulkanDevice(Device apiHandle, VulkanPhysicalDevice physicalDevice, Vk api) - { - InternalHandle = apiHandle; - Api = api; - - api.GetDeviceQueue(apiHandle, physicalDevice.QueueFamilyIndex, 0, out var queue); - - Queue = new VulkanQueue(this, queue); - - PresentQueue = Queue; - } - - public IntPtr Handle => InternalHandle.Handle; - - internal Device InternalHandle { get; } - public Vk Api { get; } - - public VulkanQueue Queue { get; private set; } - public VulkanQueue PresentQueue { get; } - - public void Dispose() - { - WaitIdle(); - Queue = null; - Api.DestroyDevice(InternalHandle, Span.Empty); - } - - internal void Submit(SubmitInfo submitInfo, Fence fence = default) - { - lock (_lock) - { - Api.QueueSubmit(Queue.InternalHandle, 1, submitInfo, fence).ThrowOnError(); - } - } - - public void WaitIdle() - { - lock (_lock) - { - Api.DeviceWaitIdle(InternalHandle); - } - } - - public void QueueWaitIdle() - { - lock (_lock) - { - Api.QueueWaitIdle(Queue.InternalHandle); - } - } - - public object Lock => _lock; - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs deleted file mode 100644 index f3116fbd..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs +++ /dev/null @@ -1,456 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using Avalonia; -using Ryujinx.Ava.Ui.Vulkan.Surfaces; -using Silk.NET.Vulkan; -using Silk.NET.Vulkan.Extensions.KHR; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - internal class VulkanDisplay : IDisposable - { - private static KhrSwapchain _swapchainExtension; - private readonly VulkanInstance _instance; - private readonly VulkanPhysicalDevice _physicalDevice; - private readonly VulkanSemaphorePair _semaphorePair; - private readonly VulkanDevice _device; - private uint _nextImage; - private readonly VulkanSurface _surface; - private SurfaceFormatKHR _surfaceFormat; - private SwapchainKHR _swapchain; - private Extent2D _swapchainExtent; - private Image[] _swapchainImages; - private ImageView[] _swapchainImageViews = Array.Empty(); - private bool _vsyncStateChanged; - private bool _vsyncEnabled; - private bool _surfaceChanged; - - public event EventHandler Presented; - - public VulkanCommandBufferPool CommandBufferPool { get; set; } - - public object Lock => _device.Lock; - - private VulkanDisplay(VulkanInstance instance, VulkanDevice device, - VulkanPhysicalDevice physicalDevice, VulkanSurface surface, SwapchainKHR swapchain, - Extent2D swapchainExtent) - { - _instance = instance; - _device = device; - _physicalDevice = physicalDevice; - _swapchain = swapchain; - _swapchainExtent = swapchainExtent; - _surface = surface; - - CreateSwapchainImages(); - - _semaphorePair = new VulkanSemaphorePair(_device); - - CommandBufferPool = new VulkanCommandBufferPool(device, physicalDevice); - } - - public PixelSize Size { get; private set; } - public uint QueueFamilyIndex => _physicalDevice.QueueFamilyIndex; - - internal SurfaceFormatKHR SurfaceFormat - { - get - { - if (_surfaceFormat.Format == Format.Undefined) - { - _surfaceFormat = _surface.GetSurfaceFormat(_physicalDevice); - } - - return _surfaceFormat; - } - } - - public void Dispose() - { - _device.WaitIdle(); - _semaphorePair?.Dispose(); - DestroyCurrentImageViews(); - _swapchainExtension.DestroySwapchain(_device.InternalHandle, _swapchain, Span.Empty); - CommandBufferPool.Dispose(); - } - - public bool IsSurfaceChanged() - { - var changed = _surfaceChanged; - _surfaceChanged = false; - - return changed; - } - - private static unsafe SwapchainKHR CreateSwapchain(VulkanInstance instance, VulkanDevice device, - VulkanPhysicalDevice physicalDevice, VulkanSurface surface, out Extent2D swapchainExtent, - SwapchainKHR? oldswapchain = null, bool vsyncEnabled = true) - { - if (_swapchainExtension == null) - { - instance.Api.TryGetDeviceExtension(instance.InternalHandle, device.InternalHandle, out _swapchainExtension); - } - - while (!surface.CanSurfacePresent(physicalDevice)) - { - Thread.Sleep(16); - } - - VulkanSurface.SurfaceExtension.GetPhysicalDeviceSurfaceCapabilities(physicalDevice.InternalHandle, - surface.ApiHandle, out var capabilities); - - var imageCount = capabilities.MinImageCount + 1; - if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount) - { - imageCount = capabilities.MaxImageCount; - } - - var surfaceFormat = surface.GetSurfaceFormat(physicalDevice); - - bool supportsIdentityTransform = capabilities.SupportedTransforms.HasFlag(SurfaceTransformFlagsKHR.SurfaceTransformIdentityBitKhr); - bool isRotated = capabilities.CurrentTransform.HasFlag(SurfaceTransformFlagsKHR.SurfaceTransformRotate90BitKhr) || - capabilities.CurrentTransform.HasFlag(SurfaceTransformFlagsKHR.SurfaceTransformRotate270BitKhr); - - swapchainExtent = GetSwapchainExtent(surface, capabilities); - - CompositeAlphaFlagsKHR compositeAlphaFlags = GetSuitableCompositeAlphaFlags(capabilities); - - PresentModeKHR presentMode = GetSuitablePresentMode(physicalDevice, surface, vsyncEnabled); - - var swapchainCreateInfo = new SwapchainCreateInfoKHR - { - SType = StructureType.SwapchainCreateInfoKhr, - Surface = surface.ApiHandle, - MinImageCount = imageCount, - ImageFormat = surfaceFormat.Format, - ImageColorSpace = surfaceFormat.ColorSpace, - ImageExtent = swapchainExtent, - ImageUsage = - ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit, - ImageSharingMode = SharingMode.Exclusive, - ImageArrayLayers = 1, - PreTransform = supportsIdentityTransform && isRotated ? - SurfaceTransformFlagsKHR.SurfaceTransformIdentityBitKhr : - capabilities.CurrentTransform, - CompositeAlpha = compositeAlphaFlags, - PresentMode = presentMode, - Clipped = true, - OldSwapchain = oldswapchain ?? new SwapchainKHR() - }; - - _swapchainExtension.CreateSwapchain(device.InternalHandle, swapchainCreateInfo, null, out var swapchain) - .ThrowOnError(); - - if (oldswapchain != null) - { - _swapchainExtension.DestroySwapchain(device.InternalHandle, oldswapchain.Value, null); - } - - return swapchain; - } - - private static unsafe Extent2D GetSwapchainExtent(VulkanSurface surface, SurfaceCapabilitiesKHR capabilities) - { - Extent2D swapchainExtent; - if (capabilities.CurrentExtent.Width != uint.MaxValue) - { - swapchainExtent = capabilities.CurrentExtent; - } - else - { - var surfaceSize = surface.SurfaceSize; - - var width = Math.Clamp((uint)surfaceSize.Width, capabilities.MinImageExtent.Width, capabilities.MaxImageExtent.Width); - var height = Math.Clamp((uint)surfaceSize.Height, capabilities.MinImageExtent.Height, capabilities.MaxImageExtent.Height); - - swapchainExtent = new Extent2D(width, height); - } - - return swapchainExtent; - } - - private static unsafe CompositeAlphaFlagsKHR GetSuitableCompositeAlphaFlags(SurfaceCapabilitiesKHR capabilities) - { - var compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaOpaqueBitKhr; - - if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.CompositeAlphaPostMultipliedBitKhr)) - { - compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaPostMultipliedBitKhr; - } - else if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.CompositeAlphaPreMultipliedBitKhr)) - { - compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaPreMultipliedBitKhr; - } - - return compositeAlphaFlags; - } - - private static unsafe PresentModeKHR GetSuitablePresentMode(VulkanPhysicalDevice physicalDevice, VulkanSurface surface, bool vsyncEnabled) - { - uint presentModesCount; - - VulkanSurface.SurfaceExtension.GetPhysicalDeviceSurfacePresentModes(physicalDevice.InternalHandle, - surface.ApiHandle, - &presentModesCount, null); - - var presentModes = new PresentModeKHR[presentModesCount]; - - fixed (PresentModeKHR* pPresentModes = presentModes) - { - VulkanSurface.SurfaceExtension.GetPhysicalDeviceSurfacePresentModes(physicalDevice.InternalHandle, - surface.ApiHandle, &presentModesCount, pPresentModes); - } - - var modes = presentModes.ToList(); - - if (!vsyncEnabled && modes.Contains(PresentModeKHR.PresentModeImmediateKhr)) - { - return PresentModeKHR.PresentModeImmediateKhr; - } - else if (modes.Contains(PresentModeKHR.PresentModeMailboxKhr)) - { - return PresentModeKHR.PresentModeMailboxKhr; - } - else if (modes.Contains(PresentModeKHR.PresentModeFifoKhr)) - { - return PresentModeKHR.PresentModeFifoKhr; - } - else - { - return PresentModeKHR.PresentModeImmediateKhr; - } - } - - internal static VulkanDisplay CreateDisplay(VulkanInstance instance, VulkanDevice device, - VulkanPhysicalDevice physicalDevice, VulkanSurface surface) - { - var swapchain = CreateSwapchain(instance, device, physicalDevice, surface, out var extent, null, true); - - return new VulkanDisplay(instance, device, physicalDevice, surface, swapchain, extent); - } - - private unsafe void CreateSwapchainImages() - { - DestroyCurrentImageViews(); - - Size = new PixelSize((int)_swapchainExtent.Width, (int)_swapchainExtent.Height); - - uint imageCount = 0; - - _swapchainExtension.GetSwapchainImages(_device.InternalHandle, _swapchain, &imageCount, null); - - _swapchainImages = new Image[imageCount]; - - fixed (Image* pSwapchainImages = _swapchainImages) - { - _swapchainExtension.GetSwapchainImages(_device.InternalHandle, _swapchain, &imageCount, pSwapchainImages); - } - - _swapchainImageViews = new ImageView[imageCount]; - - var surfaceFormat = SurfaceFormat; - - for (var i = 0; i < imageCount; i++) - { - _swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format); - } - } - - private void DestroyCurrentImageViews() - { - for (var i = 0; i < _swapchainImageViews.Length; i++) - { - _instance.Api.DestroyImageView(_device.InternalHandle, _swapchainImageViews[i], Span.Empty); - } - } - - internal void ChangeVSyncMode(bool vsyncEnabled) - { - _vsyncStateChanged = true; - _vsyncEnabled = vsyncEnabled; - } - - private void Recreate() - { - _device.WaitIdle(); - _swapchain = CreateSwapchain(_instance, _device, _physicalDevice, _surface, out _swapchainExtent, _swapchain, _vsyncEnabled); - - CreateSwapchainImages(); - - _surfaceChanged = true; - } - - private unsafe ImageView CreateSwapchainImageView(Image swapchainImage, Format format) - { - var componentMapping = new ComponentMapping( - ComponentSwizzle.Identity, - ComponentSwizzle.Identity, - ComponentSwizzle.Identity, - ComponentSwizzle.Identity); - - var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, 1, 0, 1); - - var imageCreateInfo = new ImageViewCreateInfo - { - SType = StructureType.ImageViewCreateInfo, - Image = swapchainImage, - ViewType = ImageViewType.ImageViewType2D, - Format = format, - Components = componentMapping, - SubresourceRange = subresourceRange - }; - - _instance.Api.CreateImageView(_device.InternalHandle, imageCreateInfo, null, out var imageView).ThrowOnError(); - return imageView; - } - - public bool EnsureSwapchainAvailable() - { - if (Size != _surface.SurfaceSize || _vsyncStateChanged) - { - _vsyncStateChanged = false; - - Recreate(); - - return false; - } - - return true; - } - - internal VulkanCommandBufferPool.VulkanCommandBuffer StartPresentation() - { - _nextImage = 0; - while (true) - { - var acquireResult = _swapchainExtension.AcquireNextImage( - _device.InternalHandle, - _swapchain, - ulong.MaxValue, - _semaphorePair.ImageAvailableSemaphore, - new Fence(), - ref _nextImage); - - if (acquireResult == Result.ErrorOutOfDateKhr || - acquireResult == Result.SuboptimalKhr) - { - Recreate(); - } - else - { - acquireResult.ThrowOnError(); - break; - } - } - - var commandBuffer = CommandBufferPool.CreateCommandBuffer(); - commandBuffer.BeginRecording(); - - VulkanMemoryHelper.TransitionLayout(_device, commandBuffer.InternalHandle, - _swapchainImages[_nextImage], ImageLayout.Undefined, - AccessFlags.AccessNoneKhr, - ImageLayout.TransferDstOptimal, - AccessFlags.AccessTransferWriteBit, - 1); - - return commandBuffer; - } - - internal void BlitImageToCurrentImage(VulkanSurfaceRenderTarget renderTarget, CommandBuffer commandBuffer) - { - var image = renderTarget.GetImage(); - - VulkanMemoryHelper.TransitionLayout(_device, commandBuffer, - image.InternalHandle.Value, (ImageLayout)image.CurrentLayout, - AccessFlags.AccessNoneKhr, - ImageLayout.TransferSrcOptimal, - AccessFlags.AccessTransferReadBit, - renderTarget.MipLevels); - - var srcBlitRegion = new ImageBlit - { - SrcOffsets = new ImageBlit.SrcOffsetsBuffer - { - Element0 = new Offset3D(0, 0, 0), - Element1 = new Offset3D(renderTarget.Size.Width, renderTarget.Size.Height, 1), - }, - DstOffsets = new ImageBlit.DstOffsetsBuffer - { - Element0 = new Offset3D(0, 0, 0), - Element1 = new Offset3D(Size.Width, Size.Height, 1), - }, - SrcSubresource = new ImageSubresourceLayers - { - AspectMask = ImageAspectFlags.ImageAspectColorBit, - BaseArrayLayer = 0, - LayerCount = 1, - MipLevel = 0 - }, - DstSubresource = new ImageSubresourceLayers - { - AspectMask = ImageAspectFlags.ImageAspectColorBit, - BaseArrayLayer = 0, - LayerCount = 1, - MipLevel = 0 - } - }; - - _device.Api.CmdBlitImage(commandBuffer, image.InternalHandle.Value, - ImageLayout.TransferSrcOptimal, - _swapchainImages[_nextImage], - ImageLayout.TransferDstOptimal, - 1, - srcBlitRegion, - Filter.Linear); - - VulkanMemoryHelper.TransitionLayout(_device, commandBuffer, - image.InternalHandle.Value, ImageLayout.TransferSrcOptimal, - AccessFlags.AccessTransferReadBit, - (ImageLayout)image.CurrentLayout, - AccessFlags.AccessNoneKhr, - renderTarget.MipLevels); - } - - internal unsafe void EndPresentation(VulkanCommandBufferPool.VulkanCommandBuffer commandBuffer) - { - VulkanMemoryHelper.TransitionLayout(_device, commandBuffer.InternalHandle, - _swapchainImages[_nextImage], ImageLayout.TransferDstOptimal, - AccessFlags.AccessNoneKhr, - ImageLayout.PresentSrcKhr, - AccessFlags.AccessNoneKhr, - 1); - - commandBuffer.Submit( - stackalloc[] { _semaphorePair.ImageAvailableSemaphore }, - stackalloc[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit }, - stackalloc[] { _semaphorePair.RenderFinishedSemaphore }); - - var semaphore = _semaphorePair.RenderFinishedSemaphore; - var swapchain = _swapchain; - var nextImage = _nextImage; - - Result result; - - var presentInfo = new PresentInfoKHR - { - SType = StructureType.PresentInfoKhr, - WaitSemaphoreCount = 1, - PWaitSemaphores = &semaphore, - SwapchainCount = 1, - PSwapchains = &swapchain, - PImageIndices = &nextImage, - PResults = &result - }; - - lock (_device.Lock) - { - _swapchainExtension.QueuePresent(_device.PresentQueue.InternalHandle, presentInfo); - } - - CommandBufferPool.FreeUsedCommandBuffers(); - - Presented?.Invoke(this, null); - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs deleted file mode 100644 index 3fbb8665..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System; -using Avalonia; -using Silk.NET.Vulkan; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - internal class VulkanImage : IDisposable - { - private readonly VulkanDevice _device; - private readonly VulkanPhysicalDevice _physicalDevice; - private readonly VulkanCommandBufferPool _commandBufferPool; - private ImageLayout _currentLayout; - private AccessFlags _currentAccessFlags; - private ImageUsageFlags _imageUsageFlags { get; } - private ImageView? _imageView { get; set; } - private DeviceMemory _imageMemory { get; set; } - - internal Image? InternalHandle { get; private set; } - internal Format Format { get; } - internal ImageAspectFlags AspectFlags { get; private set; } - - public ulong Handle => InternalHandle?.Handle ?? 0; - public ulong ViewHandle => _imageView?.Handle ?? 0; - public uint UsageFlags => (uint)_imageUsageFlags; - public ulong MemoryHandle => _imageMemory.Handle; - public uint MipLevels { get; private set; } - public PixelSize Size { get; } - public ulong MemorySize { get; private set; } - public uint CurrentLayout => (uint)_currentLayout; - - public VulkanImage( - VulkanDevice device, - VulkanPhysicalDevice physicalDevice, - VulkanCommandBufferPool commandBufferPool, - uint format, - PixelSize size, - uint mipLevels = 0) - { - _device = device; - _physicalDevice = physicalDevice; - _commandBufferPool = commandBufferPool; - Format = (Format)format; - Size = size; - MipLevels = mipLevels; - _imageUsageFlags = - ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit | - ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageSampledBit; - - Initialize(); - } - - public unsafe void Initialize() - { - if (!InternalHandle.HasValue) - { - MipLevels = MipLevels != 0 ? MipLevels : (uint)Math.Floor(Math.Log(Math.Max(Size.Width, Size.Height), 2)); - - var imageCreateInfo = new ImageCreateInfo - { - SType = StructureType.ImageCreateInfo, - ImageType = ImageType.ImageType2D, - Format = Format, - Extent = new Extent3D((uint?)Size.Width, (uint?)Size.Height, 1), - MipLevels = MipLevels, - ArrayLayers = 1, - Samples = SampleCountFlags.SampleCount1Bit, - Tiling = Tiling, - Usage = _imageUsageFlags, - SharingMode = SharingMode.Exclusive, - InitialLayout = ImageLayout.Undefined, - Flags = ImageCreateFlags.ImageCreateMutableFormatBit - }; - - _device.Api.CreateImage(_device.InternalHandle, imageCreateInfo, null, out var image).ThrowOnError(); - InternalHandle = image; - - _device.Api.GetImageMemoryRequirements(_device.InternalHandle, InternalHandle.Value, - out var memoryRequirements); - - var memoryAllocateInfo = new MemoryAllocateInfo - { - SType = StructureType.MemoryAllocateInfo, - AllocationSize = memoryRequirements.Size, - MemoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex( - _physicalDevice, - memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit) - }; - - _device.Api.AllocateMemory(_device.InternalHandle, memoryAllocateInfo, null, - out var imageMemory); - - _imageMemory = imageMemory; - - _device.Api.BindImageMemory(_device.InternalHandle, InternalHandle.Value, _imageMemory, 0); - - MemorySize = memoryRequirements.Size; - - var componentMapping = new ComponentMapping( - ComponentSwizzle.Identity, - ComponentSwizzle.Identity, - ComponentSwizzle.Identity, - ComponentSwizzle.Identity); - - AspectFlags = ImageAspectFlags.ImageAspectColorBit; - - var subresourceRange = new ImageSubresourceRange(AspectFlags, 0, MipLevels, 0, 1); - - var imageViewCreateInfo = new ImageViewCreateInfo - { - SType = StructureType.ImageViewCreateInfo, - Image = InternalHandle.Value, - ViewType = ImageViewType.ImageViewType2D, - Format = Format, - Components = componentMapping, - SubresourceRange = subresourceRange - }; - - _device.Api - .CreateImageView(_device.InternalHandle, imageViewCreateInfo, null, out var imageView) - .ThrowOnError(); - - _imageView = imageView; - - _currentLayout = ImageLayout.Undefined; - - TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr); - } - } - - public ImageTiling Tiling => ImageTiling.Optimal; - - internal void TransitionLayout(ImageLayout destinationLayout, AccessFlags destinationAccessFlags) - { - var commandBuffer = _commandBufferPool.CreateCommandBuffer(); - commandBuffer.BeginRecording(); - - VulkanMemoryHelper.TransitionLayout(_device, commandBuffer.InternalHandle, InternalHandle.Value, - _currentLayout, - _currentAccessFlags, - destinationLayout, destinationAccessFlags, - MipLevels); - - commandBuffer.EndRecording(); - - commandBuffer.Submit(); - - _currentLayout = destinationLayout; - _currentAccessFlags = destinationAccessFlags; - } - - public void Dispose() - { - if (InternalHandle != null) - { - _device.Api.DestroyImageView(_device.InternalHandle, _imageView.Value, Span.Empty); - _device.Api.DestroyImage(_device.InternalHandle, InternalHandle.Value, Span.Empty); - _device.Api.FreeMemory(_device.InternalHandle, _imageMemory, Span.Empty); - - _imageView = default; - InternalHandle = null; - _imageMemory = default; - } - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs deleted file mode 100644 index b50e9c07..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using Silk.NET.Core; -using Silk.NET.Vulkan; -using Silk.NET.Vulkan.Extensions.EXT; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - public class VulkanInstance : IDisposable - { - private const string EngineName = "Avalonia Vulkan"; - - private VulkanInstance(Instance apiHandle, Vk api) - { - InternalHandle = apiHandle; - Api = api; - } - - public IntPtr Handle => InternalHandle.Handle; - - internal Instance InternalHandle { get; } - public Vk Api { get; } - - internal static IEnumerable RequiredInstanceExtensions - { - get - { - yield return "VK_KHR_surface"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - yield return "VK_KHR_xlib_surface"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - yield return "VK_KHR_win32_surface"; - } - } - } - - public void Dispose() - { - Api?.DestroyInstance(InternalHandle, Span.Empty); - Api?.Dispose(); - } - - internal static unsafe VulkanInstance Create(VulkanOptions options) - { - var api = Vk.GetApi(); - var applicationName = Marshal.StringToHGlobalAnsi(options.ApplicationName); - var engineName = Marshal.StringToHGlobalAnsi(EngineName); - var enabledExtensions = new List(options.InstanceExtensions); - - enabledExtensions.AddRange(RequiredInstanceExtensions); - - var applicationInfo = new ApplicationInfo - { - PApplicationName = (byte*)applicationName, - ApiVersion = Vk.Version12.Value, - PEngineName = (byte*)engineName, - EngineVersion = new Version32(1, 0, 0), - ApplicationVersion = new Version32(1, 0, 0) - }; - - var enabledLayers = new HashSet(); - - if (options.UseDebug) - { - enabledExtensions.Add(ExtDebugUtils.ExtensionName); - enabledExtensions.Add(ExtDebugReport.ExtensionName); - if (IsLayerAvailable(api, "VK_LAYER_KHRONOS_validation")) - enabledLayers.Add("VK_LAYER_KHRONOS_validation"); - } - - foreach (var layer in options.EnabledLayers) - enabledLayers.Add(layer); - - var ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Count]; - var ppEnabledLayers = stackalloc IntPtr[enabledLayers.Count]; - - for (var i = 0; i < enabledExtensions.Count; i++) - ppEnabledExtensions[i] = Marshal.StringToHGlobalAnsi(enabledExtensions[i]); - - var layers = enabledLayers.ToList(); - - for (var i = 0; i < enabledLayers.Count; i++) - ppEnabledLayers[i] = Marshal.StringToHGlobalAnsi(layers[i]); - - var instanceCreateInfo = new InstanceCreateInfo - { - SType = StructureType.InstanceCreateInfo, - PApplicationInfo = &applicationInfo, - PpEnabledExtensionNames = (byte**)ppEnabledExtensions, - PpEnabledLayerNames = (byte**)ppEnabledLayers, - EnabledExtensionCount = (uint)enabledExtensions.Count, - EnabledLayerCount = (uint)enabledLayers.Count - }; - - api.CreateInstance(in instanceCreateInfo, null, out var instance).ThrowOnError(); - - Marshal.FreeHGlobal(applicationName); - Marshal.FreeHGlobal(engineName); - - for (var i = 0; i < enabledExtensions.Count; i++) Marshal.FreeHGlobal(ppEnabledExtensions[i]); - - for (var i = 0; i < enabledLayers.Count; i++) Marshal.FreeHGlobal(ppEnabledLayers[i]); - - return new VulkanInstance(instance, api); - } - - private static unsafe bool IsLayerAvailable(Vk api, string layerName) - { - uint layerPropertiesCount; - - api.EnumerateInstanceLayerProperties(&layerPropertiesCount, null).ThrowOnError(); - - var layerProperties = new LayerProperties[layerPropertiesCount]; - - fixed (LayerProperties* pLayerProperties = layerProperties) - { - api.EnumerateInstanceLayerProperties(&layerPropertiesCount, layerProperties).ThrowOnError(); - - for (var i = 0; i < layerPropertiesCount; i++) - { - var currentLayerName = Marshal.PtrToStringAnsi((IntPtr)pLayerProperties[i].LayerName); - - if (currentLayerName == layerName) return true; - } - } - - return false; - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanMemoryHelper.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanMemoryHelper.cs deleted file mode 100644 index a7052592..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanMemoryHelper.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Silk.NET.Vulkan; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - internal static class VulkanMemoryHelper - { - internal static int FindSuitableMemoryTypeIndex(VulkanPhysicalDevice physicalDevice, uint memoryTypeBits, - MemoryPropertyFlags flags) - { - physicalDevice.Api.GetPhysicalDeviceMemoryProperties(physicalDevice.InternalHandle, out var properties); - - for (var i = 0; i < properties.MemoryTypeCount; i++) - { - var type = properties.MemoryTypes[i]; - - if ((memoryTypeBits & (1 << i)) != 0 && type.PropertyFlags.HasFlag(flags)) return i; - } - - return -1; - } - - internal static unsafe void TransitionLayout(VulkanDevice device, - CommandBuffer commandBuffer, - Image image, - ImageLayout sourceLayout, - AccessFlags sourceAccessMask, - ImageLayout destinationLayout, - AccessFlags destinationAccessMask, - uint mipLevels) - { - var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, mipLevels, 0, 1); - - var barrier = new ImageMemoryBarrier - { - SType = StructureType.ImageMemoryBarrier, - SrcAccessMask = sourceAccessMask, - DstAccessMask = destinationAccessMask, - OldLayout = sourceLayout, - NewLayout = destinationLayout, - SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, - DstQueueFamilyIndex = Vk.QueueFamilyIgnored, - Image = image, - SubresourceRange = subresourceRange - }; - - device.Api.CmdPipelineBarrier( - commandBuffer, - PipelineStageFlags.PipelineStageAllCommandsBit, - PipelineStageFlags.PipelineStageAllCommandsBit, - 0, - 0, - null, - 0, - null, - 1, - barrier); - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs deleted file mode 100644 index 0027753c..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - public class VulkanOptions - { - /// - /// Sets the application name of the Vulkan instance - /// - public string ApplicationName { get; set; } - - /// - /// Specifies additional extensions to enable if available on the instance - /// - public IEnumerable InstanceExtensions { get; set; } = Enumerable.Empty(); - - /// - /// Specifies layers to enable if available on the instance - /// - public IEnumerable EnabledLayers { get; set; } = Enumerable.Empty(); - - /// - /// Enables the debug layer - /// - public bool UseDebug { get; set; } - - /// - /// Selects the first suitable discrete GPU available - /// - public bool PreferDiscreteGpu { get; set; } - - /// - /// Sets the device to use if available and suitable. - /// - public string PreferredDevice { get; set; } - - /// - /// Max number of device queues to request - /// - public uint MaxQueueCount { get; set; } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPhysicalDevice.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPhysicalDevice.cs deleted file mode 100644 index 11444d30..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPhysicalDevice.cs +++ /dev/null @@ -1,219 +0,0 @@ -using Ryujinx.Graphics.Vulkan; -using Silk.NET.Core; -using Silk.NET.Vulkan; -using Silk.NET.Vulkan.Extensions.KHR; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - public unsafe class VulkanPhysicalDevice - { - private VulkanPhysicalDevice(PhysicalDevice apiHandle, Vk api, uint queueCount, uint queueFamilyIndex) - { - InternalHandle = apiHandle; - Api = api; - QueueCount = queueCount; - QueueFamilyIndex = queueFamilyIndex; - - api.GetPhysicalDeviceProperties(apiHandle, out var properties); - - DeviceName = Marshal.PtrToStringAnsi((IntPtr)properties.DeviceName); - DeviceId = VulkanInitialization.StringFromIdPair(properties.VendorID, properties.DeviceID); - - var version = (Version32)properties.ApiVersion; - ApiVersion = new Version((int)version.Major, (int)version.Minor, 0, (int)version.Patch); - } - - internal PhysicalDevice InternalHandle { get; } - internal Vk Api { get; } - public uint QueueCount { get; } - public uint QueueFamilyIndex { get; } - public IntPtr Handle => InternalHandle.Handle; - - public string DeviceName { get; } - public string DeviceId { get; } - public Version ApiVersion { get; } - public static Dictionary PhysicalDevices { get; private set; } - public static IEnumerable> SuitableDevices { get; private set; } - - internal static void SelectAvailableDevices(VulkanInstance instance, - VulkanSurface surface, bool preferDiscreteGpu, string preferredDevice) - { - uint physicalDeviceCount; - - instance.Api.EnumeratePhysicalDevices(instance.InternalHandle, &physicalDeviceCount, null).ThrowOnError(); - - var physicalDevices = new PhysicalDevice[physicalDeviceCount]; - - fixed (PhysicalDevice* pPhysicalDevices = physicalDevices) - { - instance.Api.EnumeratePhysicalDevices(instance.InternalHandle, &physicalDeviceCount, pPhysicalDevices) - .ThrowOnError(); - } - - PhysicalDevices = new Dictionary(); - - foreach (var physicalDevice in physicalDevices) - { - instance.Api.GetPhysicalDeviceProperties(physicalDevice, out var properties); - PhysicalDevices.Add(physicalDevice, properties); - } - - SuitableDevices = PhysicalDevices.Where(x => IsSuitableDevice( - instance.Api, - x.Key, - x.Value, - surface.ApiHandle, - out _, - out _)); - } - - internal static VulkanPhysicalDevice FindSuitablePhysicalDevice(VulkanInstance instance, - VulkanSurface surface, bool preferDiscreteGpu, string preferredDevice) - { - SelectAvailableDevices(instance, surface, preferDiscreteGpu, preferredDevice); - - uint queueFamilyIndex = 0; - uint queueCount = 0; - - if (!string.IsNullOrWhiteSpace(preferredDevice)) - { - var physicalDevice = SuitableDevices.FirstOrDefault(x => VulkanInitialization.StringFromIdPair(x.Value.VendorID, x.Value.DeviceID) == preferredDevice); - - queueFamilyIndex = FindSuitableQueueFamily(instance.Api, physicalDevice.Key, - surface.ApiHandle, out queueCount); - if (queueFamilyIndex != int.MaxValue) - { - return new VulkanPhysicalDevice(physicalDevice.Key, instance.Api, queueCount, queueFamilyIndex); - } - } - - if (preferDiscreteGpu) - { - var discreteGpus = SuitableDevices.Where(p => p.Value.DeviceType == PhysicalDeviceType.DiscreteGpu); - - foreach (var gpu in discreteGpus) - { - queueFamilyIndex = FindSuitableQueueFamily(instance.Api, gpu.Key, - surface.ApiHandle, out queueCount); - if (queueFamilyIndex != int.MaxValue) - { - return new VulkanPhysicalDevice(gpu.Key, instance.Api, queueCount, queueFamilyIndex); - } - } - } - - foreach (var physicalDevice in SuitableDevices) - { - queueFamilyIndex = FindSuitableQueueFamily(instance.Api, physicalDevice.Key, - surface.ApiHandle, out queueCount); - if (queueFamilyIndex != int.MaxValue) - { - return new VulkanPhysicalDevice(physicalDevice.Key, instance.Api, queueCount, queueFamilyIndex); - } - } - - throw new Exception("No suitable physical device found"); - } - - private static unsafe bool IsSuitableDevice(Vk api, PhysicalDevice physicalDevice, PhysicalDeviceProperties properties, SurfaceKHR surface, - out uint queueCount, out uint familyIndex) - { - queueCount = 0; - familyIndex = 0; - - if (properties.DeviceType == PhysicalDeviceType.Cpu) return false; - - var extensionMatches = 0; - uint propertiesCount; - - api.EnumerateDeviceExtensionProperties(physicalDevice, (byte*)null, &propertiesCount, null).ThrowOnError(); - - var extensionProperties = new ExtensionProperties[propertiesCount]; - - fixed (ExtensionProperties* pExtensionProperties = extensionProperties) - { - api.EnumerateDeviceExtensionProperties( - physicalDevice, - (byte*)null, - &propertiesCount, - pExtensionProperties).ThrowOnError(); - - for (var i = 0; i < propertiesCount; i++) - { - var extensionName = Marshal.PtrToStringAnsi((IntPtr)pExtensionProperties[i].ExtensionName); - - if (VulkanInitialization.RequiredExtensions.Contains(extensionName)) - { - extensionMatches++; - } - } - } - - if (extensionMatches == VulkanInitialization.RequiredExtensions.Length) - { - familyIndex = FindSuitableQueueFamily(api, physicalDevice, surface, out queueCount); - - return familyIndex != uint.MaxValue; - } - - return false; - } - - internal unsafe string[] GetSupportedExtensions() - { - uint propertiesCount; - - Api.EnumerateDeviceExtensionProperties(InternalHandle, (byte*)null, &propertiesCount, null).ThrowOnError(); - - var extensionProperties = new ExtensionProperties[propertiesCount]; - - fixed (ExtensionProperties* pExtensionProperties = extensionProperties) - { - Api.EnumerateDeviceExtensionProperties(InternalHandle, (byte*)null, &propertiesCount, pExtensionProperties) - .ThrowOnError(); - } - - return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToArray(); - } - - private static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, - out uint queueCount) - { - const QueueFlags RequiredFlags = QueueFlags.QueueGraphicsBit | QueueFlags.QueueComputeBit; - - var khrSurface = new KhrSurface(api.Context); - - uint propertiesCount; - - api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, null); - - var properties = new QueueFamilyProperties[propertiesCount]; - - fixed (QueueFamilyProperties* pProperties = properties) - { - api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, &propertiesCount, pProperties); - } - - for (uint index = 0; index < propertiesCount; index++) - { - var queueFlags = properties[index].QueueFlags; - - khrSurface.GetPhysicalDeviceSurfaceSupport(physicalDevice, index, surface, out var surfaceSupported) - .ThrowOnError(); - - if (queueFlags.HasFlag(RequiredFlags) && surfaceSupported) - { - queueCount = properties[index].QueueCount; - return index; - } - } - - queueCount = 0; - return uint.MaxValue; - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs deleted file mode 100644 index ff8d9328..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Avalonia; -using Ryujinx.Ava.Ui.Vulkan.Surfaces; -using Ryujinx.Graphics.Vulkan; -using Silk.NET.Vulkan; -using System; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - internal class VulkanPlatformInterface : IDisposable - { - private static VulkanOptions _options; - - private VulkanPlatformInterface(VulkanInstance instance) - { - Instance = instance; - Api = instance.Api; - } - - public VulkanPhysicalDevice PhysicalDevice { get; private set; } - public VulkanInstance Instance { get; } - public Vk Api { get; private set; } - public VulkanSurfaceRenderTarget MainSurface { get; set; } - - public void Dispose() - { - Instance?.Dispose(); - Api?.Dispose(); - } - - private static VulkanPlatformInterface TryCreate() - { - _options = AvaloniaLocator.Current.GetService() ?? new VulkanOptions(); - - var instance = VulkanInstance.Create(_options); - - return new VulkanPlatformInterface(instance); - } - - public static bool TryInitialize() - { - var feature = TryCreate(); - if (feature != null) - { - AvaloniaLocator.CurrentMutable.Bind().ToConstant(feature); - return true; - } - - return false; - } - - public VulkanSurfaceRenderTarget CreateRenderTarget(IVulkanPlatformSurface platformSurface) - { - var surface = VulkanSurface.CreateSurface(Instance, platformSurface); - - if (PhysicalDevice == null) - { - PhysicalDevice = VulkanPhysicalDevice.FindSuitablePhysicalDevice(Instance, surface, _options.PreferDiscreteGpu, _options.PreferredDevice); - } - - var renderTarget = new VulkanSurfaceRenderTarget(this, surface); - - if (MainSurface == null && surface != null) - { - MainSurface = renderTarget; - } - - return renderTarget; - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanQueue.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanQueue.cs deleted file mode 100644 index a903e21a..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanQueue.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Silk.NET.Vulkan; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - internal class VulkanQueue - { - public VulkanQueue(VulkanDevice device, Queue apiHandle) - { - Device = device; - InternalHandle = apiHandle; - } - - public VulkanDevice Device { get; } - public IntPtr Handle => InternalHandle.Handle; - internal Queue InternalHandle { get; } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSemaphorePair.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSemaphorePair.cs deleted file mode 100644 index 3b5fd9cc..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSemaphorePair.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Silk.NET.Vulkan; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - internal class VulkanSemaphorePair : IDisposable - { - private readonly VulkanDevice _device; - - public unsafe VulkanSemaphorePair(VulkanDevice device) - { - _device = device; - - var semaphoreCreateInfo = new SemaphoreCreateInfo { SType = StructureType.SemaphoreCreateInfo }; - - _device.Api.CreateSemaphore(_device.InternalHandle, semaphoreCreateInfo, null, out var semaphore).ThrowOnError(); - ImageAvailableSemaphore = semaphore; - - _device.Api.CreateSemaphore(_device.InternalHandle, semaphoreCreateInfo, null, out semaphore).ThrowOnError(); - RenderFinishedSemaphore = semaphore; - } - - internal Semaphore ImageAvailableSemaphore { get; } - internal Semaphore RenderFinishedSemaphore { get; } - - public unsafe void Dispose() - { - _device.Api.DestroySemaphore(_device.InternalHandle, ImageAvailableSemaphore, null); - _device.Api.DestroySemaphore(_device.InternalHandle, RenderFinishedSemaphore, null); - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurface.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurface.cs deleted file mode 100644 index 2452cdcd..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurface.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using Avalonia; -using Ryujinx.Ava.Ui.Vulkan.Surfaces; -using Silk.NET.Vulkan; -using Silk.NET.Vulkan.Extensions.KHR; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - public class VulkanSurface : IDisposable - { - private readonly VulkanInstance _instance; - private readonly IVulkanPlatformSurface _vulkanPlatformSurface; - - private VulkanSurface(IVulkanPlatformSurface vulkanPlatformSurface, VulkanInstance instance) - { - _vulkanPlatformSurface = vulkanPlatformSurface; - _instance = instance; - ApiHandle = vulkanPlatformSurface.CreateSurface(instance); - } - - internal SurfaceKHR ApiHandle { get; } - - internal static KhrSurface SurfaceExtension { get; private set; } - - internal PixelSize SurfaceSize => _vulkanPlatformSurface.SurfaceSize; - - public void Dispose() - { - SurfaceExtension.DestroySurface(_instance.InternalHandle, ApiHandle, Span.Empty); - _vulkanPlatformSurface.Dispose(); - } - - internal static VulkanSurface CreateSurface(VulkanInstance instance, IVulkanPlatformSurface vulkanPlatformSurface) - { - if (SurfaceExtension == null) - { - instance.Api.TryGetInstanceExtension(instance.InternalHandle, out KhrSurface extension); - - SurfaceExtension = extension; - } - - return new VulkanSurface(vulkanPlatformSurface, instance); - } - - internal bool CanSurfacePresent(VulkanPhysicalDevice physicalDevice) - { - SurfaceExtension.GetPhysicalDeviceSurfaceSupport(physicalDevice.InternalHandle, physicalDevice.QueueFamilyIndex, ApiHandle, out var isSupported); - - return isSupported; - } - - internal SurfaceFormatKHR GetSurfaceFormat(VulkanPhysicalDevice physicalDevice) - { - Span surfaceFormatsCount = stackalloc uint[1]; - SurfaceExtension.GetPhysicalDeviceSurfaceFormats(physicalDevice.InternalHandle, ApiHandle, surfaceFormatsCount, Span.Empty); - Span surfaceFormats = stackalloc SurfaceFormatKHR[(int)surfaceFormatsCount[0]]; - SurfaceExtension.GetPhysicalDeviceSurfaceFormats(physicalDevice.InternalHandle, ApiHandle, surfaceFormatsCount, surfaceFormats); - - if (surfaceFormats.Length == 1 && surfaceFormats[0].Format == Format.Undefined) - { - return new SurfaceFormatKHR(Format.B8G8R8A8Unorm, ColorSpaceKHR.ColorspaceSrgbNonlinearKhr); - } - - foreach (var format in surfaceFormats) - { - if (format.Format == Format.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.ColorspaceSrgbNonlinearKhr) - { - return format; - } - } - - return surfaceFormats[0]; - } - } -} diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurfaceRenderingSession.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurfaceRenderingSession.cs deleted file mode 100644 index 71f5f18a..00000000 --- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurfaceRenderingSession.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using Avalonia; -using Ryujinx.Ava.Ui.Vulkan.Surfaces; -using Silk.NET.Vulkan; - -namespace Ryujinx.Ava.Ui.Vulkan -{ - internal class VulkanSurfaceRenderingSession : IDisposable - { - private readonly VulkanDevice _device; - private readonly VulkanSurfaceRenderTarget _renderTarget; - - public VulkanSurfaceRenderingSession(VulkanDisplay display, VulkanDevice device, - VulkanSurfaceRenderTarget renderTarget, float scaling) - { - Display = display; - _device = device; - _renderTarget = renderTarget; - Scaling = scaling; - Begin(); - } - - public VulkanDisplay Display { get; } - - public PixelSize Size => _renderTarget.Size; - public Vk Api => _device.Api; - - public float Scaling { get; } - - private void Begin() - { - if (!Display.EnsureSwapchainAvailable()) - { - _renderTarget.RecreateImage(); - } - } - - public void Dispose() - { - _renderTarget.EndDraw(); - } - } -} 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 WindowCreated; + public event EventHandler 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(), + 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() - .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 RendererInitialized; - public event EventHandler 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 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 @@ + + + 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 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/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 _imagesInFlight; - private PresentImageInfo _currentImage; - - public VulkanRendererControl(GraphicsDebugLevel graphicsDebugLevel) : base(graphicsDebugLevel) - { - _platformInterface = AvaloniaLocator.Current.GetService(); - - _imagesInFlight = new ConcurrentQueue(); - } - - 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(); - - 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(); - - 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() + }; + } + } + + [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); + } +} diff --git a/Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs b/Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs index 32f08ff9..10dd2da3 100644 --- a/Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs +++ b/Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs @@ -10,7 +10,6 @@ using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Input; using Ryujinx.Ava.Ui.Controls; -using Ryujinx.Ava.Ui.Vulkan; using Ryujinx.Ava.Ui.Windows; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; @@ -252,34 +251,19 @@ namespace Ryujinx.Ava.Ui.ViewModels { _gpuIds = new List(); List names = new List(); - if (!Program.UseVulkan) - { - var devices = VulkanRenderer.GetPhysicalDevices(); + var devices = VulkanRenderer.GetPhysicalDevices(); - if (devices.Length == 0) - { - IsVulkanAvailable = false; - GraphicsBackendIndex = 1; - } - else - { - foreach (var device in devices) - { - _gpuIds.Add(device.Id); - names.Add($"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}"); - } - } + if (devices.Length == 0) + { + IsVulkanAvailable = false; + GraphicsBackendIndex = 1; } else { - foreach (var device in VulkanPhysicalDevice.SuitableDevices) + foreach (var device in devices) { - _gpuIds.Add( - VulkanInitialization.StringFromIdPair(device.Value.VendorID, device.Value.DeviceID)); - var value = device.Value; - var name = value.DeviceName; - names.Add( - $"{Marshal.PtrToStringAnsi((IntPtr)name)} {(device.Value.DeviceType == PhysicalDeviceType.DiscreteGpu ? "(dGPU)" : "")}"); + _gpuIds.Add(device.Id); + names.Add($"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}"); } } @@ -407,7 +391,7 @@ namespace Ryujinx.Ava.Ui.ViewModels _previousVolumeLevel = Volume; } - public async Task SaveSettings() + public void SaveSettings() { ConfigurationState config = ConfigurationState.Instance; @@ -422,8 +406,6 @@ namespace Ryujinx.Ava.Ui.ViewModels config.System.TimeZone.Value = TimeZone; } - bool requiresRestart = config.Graphics.GraphicsBackend.Value != (GraphicsBackend)GraphicsBackendIndex; - config.Logger.EnableError.Value = EnableError; config.Logger.EnableTrace.Value = EnableTrace; config.Logger.EnableWarn.Value = EnableWarn; @@ -456,19 +438,7 @@ namespace Ryujinx.Ava.Ui.ViewModels config.System.Language.Value = (Language)Language; config.System.Region.Value = (Region)Region; - var selectedGpu = _gpuIds.ElementAtOrDefault(PreferredGpuIndex); - if (!requiresRestart) - { - var platform = AvaloniaLocator.Current.GetService(); - if (platform != null) - { - var physicalDevice = platform.PhysicalDevice; - - requiresRestart = physicalDevice.DeviceId != selectedGpu; - } - } - - config.Graphics.PreferredGpu.Value = selectedGpu; + config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex); if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex) { @@ -507,20 +477,6 @@ namespace Ryujinx.Ava.Ui.ViewModels MainWindow.UpdateGraphicsConfig(); _previousVolumeLevel = Volume; - - if (requiresRestart) - { - var choice = await ContentDialogHelper.CreateChoiceDialog( - LocaleManager.Instance["SettingsAppRequiredRestartMessage"], - LocaleManager.Instance["SettingsGpuBackendRestartMessage"], - LocaleManager.Instance["SettingsGpuBackendRestartSubMessage"]); - - if (choice) - { - Process.Start(Environment.ProcessPath); - Environment.Exit(0); - } - } } public void RevertIfNotSaved() diff --git a/Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml b/Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml new file mode 100644 index 00000000..2967f4f2 --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml.cs new file mode 100644 index 00000000..7a51e64d --- /dev/null +++ b/Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml.cs @@ -0,0 +1,25 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Media; + +namespace Ryujinx.Ava.Ui.Windows +{ + public partial class ContentDialogOverlayWindow : StyleableWindow + { + public ContentDialogOverlayWindow() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + ExtendClientAreaToDecorationsHint = true; + TransparencyLevelHint = WindowTransparencyLevel.Transparent; + WindowStartupLocation = WindowStartupLocation.Manual; + SystemDecorations = SystemDecorations.None; + ExtendClientAreaTitleBarHeightHint = 0; + Background = Brushes.Transparent; + CanResize = false; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml index 610281a6..6eafd5e5 100644 --- a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml +++ b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml @@ -462,6 +462,7 @@ VerticalAlignment="Stretch" Background="{DynamicResource ThemeContentBackgroundColor}" IsVisible="{Binding ShowLoadProgress}" + Name="LoadingView" ZIndex="1000"> @@ -346,17 +355,17 @@ namespace Ryujinx.Ava.Ui.Windows Dispatcher.UIThread.InvokeAsync(() => { - if (MainContent.Content != _mainViewContent) - { - MainContent.Content = _mainViewContent; - } - ViewModel.ShowMenuAndStatusBar = true; ViewModel.ShowContent = true; ViewModel.ShowLoadProgress = false; ViewModel.IsLoadingIndeterminate = false; Cursor = Cursor.Default; + if (MainContent.Content != _mainViewContent) + { + MainContent.Content = _mainViewContent; + } + AppHost = null; HandleRelaunch(); diff --git a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs index 5b2ea276..73ac0624 100644 --- a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs +++ b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml.cs @@ -211,9 +211,9 @@ namespace Ryujinx.Ava.Ui.Windows } } - private async void SaveButton_Clicked(object sender, RoutedEventArgs e) + private void SaveButton_Clicked(object sender, RoutedEventArgs e) { - await SaveSettings(); + SaveSettings(); Close(); } @@ -224,14 +224,14 @@ namespace Ryujinx.Ava.Ui.Windows Close(); } - private async void ApplyButton_Clicked(object sender, RoutedEventArgs e) + private void ApplyButton_Clicked(object sender, RoutedEventArgs e) { - await SaveSettings(); + SaveSettings(); } - private async Task SaveSettings() + private void SaveSettings() { - await ViewModel.SaveSettings(); + ViewModel.SaveSettings(); ControllerSettings?.SaveCurrentProfile(); diff --git a/Ryujinx.Graphics.GAL/IWindow.cs b/Ryujinx.Graphics.GAL/IWindow.cs index 043193c9..a9bbbc5e 100644 --- a/Ryujinx.Graphics.GAL/IWindow.cs +++ b/Ryujinx.Graphics.GAL/IWindow.cs @@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.GAL { public interface IWindow { - void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback); + void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback); void SetSize(int width, int height); diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs index f0fec173..c4f3b553 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs @@ -9,9 +9,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Window public CommandType CommandType => CommandType.WindowPresent; private TableRef _texture; private ImageCrop _crop; - private TableRef> _swapBuffersCallback; + private TableRef _swapBuffersCallback; - public void Set(TableRef texture, ImageCrop crop, TableRef> swapBuffersCallback) + public void Set(TableRef texture, ImageCrop crop, TableRef swapBuffersCallback) { _texture = texture; _crop = crop; diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs index 21a66e7c..c4b62a25 100644 --- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs +++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs @@ -16,13 +16,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading _impl = impl; } - public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) + public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) { // If there's already a frame in the pipeline, wait for it to be presented first. // This is a multithread rate limit - we can't be more than one frame behind the command queue. _renderer.WaitForFrame(); - _renderer.New().Set(new TableRef(_renderer, texture as ThreadedTexture), crop, new TableRef>(_renderer, swapBuffersCallback)); + _renderer.New().Set(new TableRef(_renderer, texture as ThreadedTexture), crop, new TableRef(_renderer, swapBuffersCallback)); _renderer.QueueCommand(); } diff --git a/Ryujinx.Graphics.Gpu/Window.cs b/Ryujinx.Graphics.Gpu/Window.cs index 922583cf..8ad70c7f 100644 --- a/Ryujinx.Graphics.Gpu/Window.cs +++ b/Ryujinx.Graphics.Gpu/Window.cs @@ -191,7 +191,7 @@ namespace Ryujinx.Graphics.Gpu /// If the queue is empty, then no texture is presented. /// /// Callback method to call when a new texture should be presented on the screen - public void Present(Action swapBuffersCallback) + public void Present(Action swapBuffersCallback) { _context.AdvanceSequence(); diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs index edebf1a0..61b739b1 100644 --- a/Ryujinx.Graphics.OpenGL/Window.cs +++ b/Ryujinx.Graphics.OpenGL/Window.cs @@ -12,11 +12,7 @@ namespace Ryujinx.Graphics.OpenGL private int _width; private int _height; - private bool _sizeChanged; private int _copyFramebufferHandle; - private int _stagingFrameBuffer; - private int[] _stagingTextures; - private int _currentTexture; internal BackgroundContextWorker BackgroundContext { get; private set; } @@ -25,28 +21,15 @@ namespace Ryujinx.Graphics.OpenGL public Window(OpenGLRenderer renderer) { _renderer = renderer; - _stagingTextures = new int[TextureCount]; } - public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) + public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) { GL.Disable(EnableCap.FramebufferSrgb); - if (_sizeChanged) - { - if (_stagingFrameBuffer != 0) - { - GL.DeleteTextures(_stagingTextures.Length, _stagingTextures); - GL.DeleteFramebuffer(_stagingFrameBuffer); - } - - CreateStagingFramebuffer(); - _sizeChanged = false; - } - (int oldDrawFramebufferHandle, int oldReadFramebufferHandle) = ((Pipeline)_renderer.Pipeline).GetBoundFramebuffers(); - CopyTextureToFrameBufferRGB(_stagingFrameBuffer, GetCopyFramebufferHandleLazy(), (TextureView)texture, crop, swapBuffersCallback); + CopyTextureToFrameBufferRGB(0, GetCopyFramebufferHandleLazy(), (TextureView)texture, crop, swapBuffersCallback); GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle); GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle); @@ -59,41 +42,17 @@ namespace Ryujinx.Graphics.OpenGL public void ChangeVSyncMode(bool vsyncEnabled) { } - private void CreateStagingFramebuffer() - { - _stagingFrameBuffer = GL.GenFramebuffer(); - GL.GenTextures(_stagingTextures.Length, _stagingTextures); - - GL.BindFramebuffer(FramebufferTarget.Framebuffer, _stagingFrameBuffer); - - foreach (var stagingTexture in _stagingTextures) - { - GL.BindTexture(TextureTarget.Texture2D, stagingTexture); - GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, _width, _height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); - - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); - GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); - GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, stagingTexture, 0); - } - - GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); - GL.BindTexture(TextureTarget.Texture2D, 0); - } - public void SetSize(int width, int height) { _width = width; _height = height; - _sizeChanged = true; } - private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop, Action swapBuffersCallback) + private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop, Action swapBuffersCallback) { GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer); GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer); - GL.FramebufferTexture2D(FramebufferTarget.DrawFramebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, _stagingTextures[_currentTexture], 0); - TextureView viewConverted = view.Format.IsBgr() ? _renderer.TextureCopy.BgraSwap(view) : view; GL.FramebufferTexture( @@ -189,12 +148,8 @@ namespace Ryujinx.Graphics.OpenGL // Set clip control, viewport and the framebuffer to the output to placate overlays and OBS capture. GL.ClipControl(ClipOrigin.LowerLeft, ClipDepthMode.NegativeOneToOne); GL.Viewport(0, 0, _width, _height); - GL.BindFramebuffer(FramebufferTarget.Framebuffer, drawFramebuffer); - GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, _stagingFrameBuffer); - - swapBuffersCallback((object)_stagingTextures[_currentTexture]); - _currentTexture = ++_currentTexture % _stagingTextures.Length; + swapBuffersCallback(); ((Pipeline)_renderer.Pipeline).RestoreClipControl(); ((Pipeline)_renderer.Pipeline).RestoreScissor0Enable(); @@ -246,14 +201,6 @@ namespace Ryujinx.Graphics.OpenGL _copyFramebufferHandle = 0; } - - if (_stagingFrameBuffer != 0) - { - GL.DeleteTextures(_stagingTextures.Length, _stagingTextures); - GL.DeleteFramebuffer(_stagingFrameBuffer); - _stagingFrameBuffer = 0; - _stagingTextures = null; - } } } } diff --git a/Ryujinx.Graphics.Vulkan/ImageWindow.cs b/Ryujinx.Graphics.Vulkan/ImageWindow.cs deleted file mode 100644 index 69302fdf..00000000 --- a/Ryujinx.Graphics.Vulkan/ImageWindow.cs +++ /dev/null @@ -1,429 +0,0 @@ -using Ryujinx.Graphics.GAL; -using Silk.NET.Vulkan; -using System; -using VkFormat = Silk.NET.Vulkan.Format; - -namespace Ryujinx.Graphics.Vulkan -{ - class ImageWindow : WindowBase, IWindow, IDisposable - { - internal const VkFormat Format = VkFormat.R8G8B8A8Unorm; - - private const int ImageCount = 3; - private const int SurfaceWidth = 1280; - private const int SurfaceHeight = 720; - - private readonly VulkanRenderer _gd; - private readonly PhysicalDevice _physicalDevice; - private readonly Device _device; - - private Auto[] _images; - private Auto[] _imageViews; - private Auto[] _imageAllocationAuto; - private ImageState[] _states; - private PresentImageInfo[] _presentedImages; - private FenceHolder[] _fences; - - private ulong[] _imageSizes; - private ulong[] _imageOffsets; - - private int _width = SurfaceWidth; - private int _height = SurfaceHeight; - private bool _recreateImages; - private int _nextImage; - - public unsafe ImageWindow(VulkanRenderer gd, PhysicalDevice physicalDevice, Device device) - { - _gd = gd; - _physicalDevice = physicalDevice; - _device = device; - - _images = new Auto[ImageCount]; - _imageAllocationAuto = new Auto[ImageCount]; - _imageSizes = new ulong[ImageCount]; - _imageOffsets = new ulong[ImageCount]; - _states = new ImageState[ImageCount]; - _presentedImages = new PresentImageInfo[ImageCount]; - - CreateImages(); - } - - private void RecreateImages() - { - for (int i = 0; i < ImageCount; i++) - { - lock (_states[i]) - { - _states[i].IsValid = false; - _fences[i]?.Wait(); - _fences[i]?.Put(); - _imageViews[i]?.Dispose(); - _imageAllocationAuto[i]?.Dispose(); - _images[i]?.Dispose(); - } - } - _presentedImages = null; - - CreateImages(); - } - - private unsafe void CreateImages() - { - _imageViews = new Auto[ImageCount]; - _fences = new FenceHolder[ImageCount]; - _presentedImages = new PresentImageInfo[ImageCount]; - - _nextImage = 0; - var cbs = _gd.CommandBufferPool.Rent(); - - var imageCreateInfo = new ImageCreateInfo - { - SType = StructureType.ImageCreateInfo, - ImageType = ImageType.ImageType2D, - Format = Format, - Extent = new Extent3D((uint?)_width, (uint?)_height, 1), - MipLevels = 1, - ArrayLayers = 1, - Samples = SampleCountFlags.SampleCount1Bit, - Tiling = ImageTiling.Optimal, - Usage = ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageTransferDstBit, - SharingMode = SharingMode.Exclusive, - InitialLayout = ImageLayout.Undefined, - Flags = ImageCreateFlags.ImageCreateMutableFormatBit - }; - - for (int i = 0; i < _images.Length; i++) - { - _gd.Api.CreateImage(_device, imageCreateInfo, null, out var image).ThrowOnError(); - _images[i] = new Auto(new DisposableImage(_gd.Api, _device, image)); - - _gd.Api.GetImageMemoryRequirements(_device, image, - out var memoryRequirements); - var allocation = _gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, memoryRequirements, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit); - - _imageSizes[i] = allocation.Size; - _imageOffsets[i] = allocation.Offset; - - _imageAllocationAuto[i] = new Auto(allocation); - - _gd.Api.BindImageMemory(_device, image, allocation.Memory, allocation.Offset); - - _imageViews[i] = CreateImageView(image, Format); - - Transition( - cbs.CommandBuffer, - image, - 0, - 0, - ImageLayout.Undefined, - ImageLayout.TransferSrcOptimal); - - _states[i] = new ImageState(); - } - - _gd.CommandBufferPool.Return(cbs); - } - - private unsafe Auto CreateImageView(Image image, VkFormat format) - { - var componentMapping = new ComponentMapping( - ComponentSwizzle.R, - ComponentSwizzle.G, - ComponentSwizzle.B, - ComponentSwizzle.A); - - var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, 1, 0, 1); - - var imageCreateInfo = new ImageViewCreateInfo() - { - SType = StructureType.ImageViewCreateInfo, - Image = image, - ViewType = ImageViewType.ImageViewType2D, - Format = format, - Components = componentMapping, - SubresourceRange = subresourceRange - }; - - _gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError(); - return new Auto(new DisposableImageView(_gd.Api, _device, imageView)); - } - - public override unsafe void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) - { - if (_recreateImages) - { - RecreateImages(); - _recreateImages = false; - } - - var image = _images[_nextImage]; - - _gd.FlushAllCommands(); - - var cbs = _gd.CommandBufferPool.Rent(); - - Transition( - cbs.CommandBuffer, - image.GetUnsafe().Value, - 0, - AccessFlags.AccessTransferWriteBit, - ImageLayout.TransferSrcOptimal, - ImageLayout.General); - - var view = (TextureView)texture; - - int srcX0, srcX1, srcY0, srcY1; - float scale = view.ScaleFactor; - - if (crop.Left == 0 && crop.Right == 0) - { - srcX0 = 0; - srcX1 = (int)(view.Width / scale); - } - else - { - srcX0 = crop.Left; - srcX1 = crop.Right; - } - - if (crop.Top == 0 && crop.Bottom == 0) - { - srcY0 = 0; - srcY1 = (int)(view.Height / scale); - } - else - { - srcY0 = crop.Top; - srcY1 = crop.Bottom; - } - - if (scale != 1f) - { - srcX0 = (int)(srcX0 * scale); - srcY0 = (int)(srcY0 * scale); - srcX1 = (int)Math.Ceiling(srcX1 * scale); - srcY1 = (int)Math.Ceiling(srcY1 * scale); - } - - if (ScreenCaptureRequested) - { - CaptureFrame(view, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, view.Info.Format.IsBgr(), crop.FlipX, crop.FlipY); - - ScreenCaptureRequested = false; - } - - float ratioX = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _height * crop.AspectRatioX / (_width * crop.AspectRatioY)); - float ratioY = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _width * crop.AspectRatioY / (_height * crop.AspectRatioX)); - - int dstWidth = (int)(_width * ratioX); - int dstHeight = (int)(_height * ratioY); - - int dstPaddingX = (_width - dstWidth) / 2; - int dstPaddingY = (_height - dstHeight) / 2; - - int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX; - int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX; - - int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY; - int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY; - - _gd.HelperShader.Blit( - _gd, - cbs, - view, - _imageViews[_nextImage], - _width, - _height, - Format, - new Extents2D(srcX0, srcY0, srcX1, srcY1), - new Extents2D(dstX0, dstY1, dstX1, dstY0), - true, - true); - - Transition( - cbs.CommandBuffer, - image.GetUnsafe().Value, - 0, - 0, - ImageLayout.General, - ImageLayout.TransferSrcOptimal); - - _gd.CommandBufferPool.Return( - cbs, - null, - stackalloc[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit }, - null); - - _fences[_nextImage]?.Put(); - _fences[_nextImage] = cbs.GetFence(); - cbs.GetFence().Get(); - - PresentImageInfo info = _presentedImages[_nextImage]; - - if (info == null) - { - info = new PresentImageInfo( - image, - _imageAllocationAuto[_nextImage], - _device, - _physicalDevice, - _imageSizes[_nextImage], - _imageOffsets[_nextImage], - new Extent2D((uint)_width, (uint)_height), - _states[_nextImage]); - - _presentedImages[_nextImage] = info; - } - - swapBuffersCallback(info); - - _nextImage = (_nextImage + 1) % ImageCount; - } - - private unsafe void Transition( - CommandBuffer commandBuffer, - Image image, - AccessFlags srcAccess, - AccessFlags dstAccess, - ImageLayout srcLayout, - ImageLayout dstLayout) - { - var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, 1, 0, 1); - - var barrier = new ImageMemoryBarrier() - { - SType = StructureType.ImageMemoryBarrier, - SrcAccessMask = srcAccess, - DstAccessMask = dstAccess, - OldLayout = srcLayout, - NewLayout = dstLayout, - SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, - DstQueueFamilyIndex = Vk.QueueFamilyIgnored, - Image = image, - SubresourceRange = subresourceRange - }; - - _gd.Api.CmdPipelineBarrier( - commandBuffer, - PipelineStageFlags.PipelineStageTopOfPipeBit, - PipelineStageFlags.PipelineStageAllCommandsBit, - 0, - 0, - null, - 0, - null, - 1, - barrier); - } - - private void CaptureFrame(TextureView texture, int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY) - { - byte[] bitmap = texture.GetData(x, y, width, height); - - _gd.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY)); - } - - public override void SetSize(int width, int height) - { - if (_width != width || _height != height) - { - _recreateImages = true; - } - - _width = width; - _height = height; - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - unsafe - { - for (int i = 0; i < ImageCount; i++) - { - _states[i].IsValid = false; - _fences[i]?.Wait(); - _fences[i]?.Put(); - _imageViews[i]?.Dispose(); - _imageAllocationAuto[i]?.Dispose(); - _images[i]?.Dispose(); - } - } - } - } - - public override void Dispose() - { - Dispose(true); - } - - public override void ChangeVSyncMode(bool vsyncEnabled) { } - } - - public class ImageState - { - private bool _isValid = true; - - public bool IsValid - { - get => _isValid; - internal set - { - _isValid = value; - - StateChanged?.Invoke(this, _isValid); - } - } - - public event EventHandler StateChanged; - } - - public class PresentImageInfo - { - private readonly Auto _image; - private readonly Auto _memory; - - public Image Image => _image.GetUnsafe().Value; - - public DeviceMemory Memory => _memory.GetUnsafe().Memory; - - public Device Device { get; } - public PhysicalDevice PhysicalDevice { get; } - public ulong MemorySize { get; } - public ulong MemoryOffset { get; } - public Extent2D Extent { get; } - public ImageState State { get; internal set; } - internal PresentImageInfo( - Auto image, - Auto memory, - Device device, - PhysicalDevice physicalDevice, - ulong memorySize, - ulong memoryOffset, - Extent2D extent2D, - ImageState state) - { - _image = image; - _memory = memory; - Device = device; - PhysicalDevice = physicalDevice; - MemorySize = memorySize; - MemoryOffset = memoryOffset; - Extent = extent2D; - State = state; - } - - public void Get() - { - _memory.IncrementReferenceCount(); - _image.IncrementReferenceCount(); - } - - public void Put() - { - _memory.DecrementReferenceCount(); - _image.DecrementReferenceCount(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 5abe1be1..3776be9e 100644 --- a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -20,7 +20,6 @@ namespace Ryujinx.Graphics.Vulkan private SurfaceKHR _surface; private PhysicalDevice _physicalDevice; private Device _device; - private uint _queueFamilyIndex; private WindowBase _window; internal FormatCapabilities FormatCapabilities { get; private set; } @@ -37,7 +36,6 @@ namespace Ryujinx.Graphics.Vulkan internal ExtDebugReport DebugReportApi { get; private set; } internal uint QueueFamilyIndex { get; private set; } - public bool IsOffScreen { get; } internal Queue Queue { get; private set; } internal Queue BackgroundQueue { get; private set; } internal object BackgroundQueueLock { get; private set; } @@ -94,22 +92,6 @@ namespace Ryujinx.Graphics.Vulkan Samplers = new HashSet(); } - public VulkanRenderer(Instance instance, Device device, PhysicalDevice physicalDevice, Queue queue, uint queueFamilyIndex, object lockObject) - { - _instance = instance; - _physicalDevice = physicalDevice; - _device = device; - _queueFamilyIndex = queueFamilyIndex; - - Queue = queue; - QueueLock = lockObject; - - IsOffScreen = true; - Shaders = new HashSet(); - Textures = new HashSet(); - Samplers = new HashSet(); - } - private unsafe void LoadFeatures(string[] supportedExtensions, uint maxQueueCount, uint queueFamilyIndex) { FormatCapabilities = new FormatCapabilities(Api, _physicalDevice); @@ -286,34 +268,6 @@ namespace Ryujinx.Graphics.Vulkan _window = new Window(this, _surface, _physicalDevice, _device); } - private unsafe void SetupOffScreenContext(GraphicsDebugLevel logLevel) - { - var api = Vk.GetApi(); - - Api = api; - - VulkanInitialization.CreateDebugCallbacks(api, logLevel, _instance, out var debugReport, out _debugReportCallback); - - DebugReportApi = debugReport; - - var supportedExtensions = VulkanInitialization.GetSupportedExtensions(api, _physicalDevice); - - uint propertiesCount; - - api.GetPhysicalDeviceQueueFamilyProperties(_physicalDevice, &propertiesCount, null); - - QueueFamilyProperties[] queueFamilyProperties = new QueueFamilyProperties[propertiesCount]; - - fixed (QueueFamilyProperties* pProperties = queueFamilyProperties) - { - api.GetPhysicalDeviceQueueFamilyProperties(_physicalDevice, &propertiesCount, pProperties); - } - - LoadFeatures(supportedExtensions, queueFamilyProperties[0].QueueCount, _queueFamilyIndex); - - _window = new ImageWindow(this, _physicalDevice, _device); - } - public BufferHandle CreateBuffer(int size) { return BufferManager.CreateWithHandle(this, size, false); @@ -519,14 +473,7 @@ namespace Ryujinx.Graphics.Vulkan public void Initialize(GraphicsDebugLevel logLevel) { - if (IsOffScreen) - { - SetupOffScreenContext(logLevel); - } - else - { - SetupContext(logLevel); - } + SetupContext(logLevel); PrintGpuInformation(); } @@ -638,15 +585,12 @@ namespace Ryujinx.Graphics.Vulkan sampler.Dispose(); } - if (!IsOffScreen) - { - SurfaceApi.DestroySurface(_instance, _surface, null); + SurfaceApi.DestroySurface(_instance, _surface, null); - Api.DestroyDevice(_device, null); + Api.DestroyDevice(_device, null); - // Last step destroy the instance - Api.DestroyInstance(_instance, null); - } + // Last step destroy the instance + Api.DestroyInstance(_instance, null); } } } diff --git a/Ryujinx.Graphics.Vulkan/Window.cs b/Ryujinx.Graphics.Vulkan/Window.cs index 26f53b39..71b54204 100644 --- a/Ryujinx.Graphics.Vulkan/Window.cs +++ b/Ryujinx.Graphics.Vulkan/Window.cs @@ -217,7 +217,7 @@ namespace Ryujinx.Graphics.Vulkan } } - public unsafe override void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) + public unsafe override void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback) { uint nextImage = 0; diff --git a/Ryujinx.Graphics.Vulkan/WindowBase.cs b/Ryujinx.Graphics.Vulkan/WindowBase.cs index 80b5c0e3..651fe7c1 100644 --- a/Ryujinx.Graphics.Vulkan/WindowBase.cs +++ b/Ryujinx.Graphics.Vulkan/WindowBase.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Vulkan public bool ScreenCaptureRequested { get; set; } public abstract void Dispose(); - public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback); + public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback); public abstract void SetSize(int width, int height); public abstract void ChangeVSyncMode(bool vsyncEnabled); } diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 8ea595e8..4bd1fe39 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -117,7 +117,7 @@ namespace Ryujinx.HLE return Gpu.Window.ConsumeFrameAvailable(); } - public void PresentFrame(Action swapBuffersCallback) + public void PresentFrame(Action swapBuffersCallback) { Gpu.Window.Present(swapBuffersCallback); } diff --git a/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs b/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs index aa2e86d9..d1d0872b 100644 --- a/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs +++ b/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs @@ -136,7 +136,7 @@ namespace Ryujinx.Headless.SDL2.OpenGL GL.ClearColor(0, 0, 0, 1.0f); GL.Clear(ClearBufferMask.ColorBufferBit); - SwapBuffers(0); + SwapBuffers(); Renderer?.Window.SetSize(DefaultWidth, DefaultHeight); MouseDriver.SetClientSize(DefaultWidth, DefaultHeight); @@ -156,28 +156,8 @@ namespace Ryujinx.Headless.SDL2.OpenGL _openGLContext.Dispose(); } - protected override void SwapBuffers(object image) + protected override void SwapBuffers() { - if ((int)image != 0) - { - // The game's framebruffer is already bound, so blit it to the window's backbuffer - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); - - GL.Clear(ClearBufferMask.ColorBufferBit); - GL.ClearColor(0, 0, 0, 1); - - GL.BlitFramebuffer(0, - 0, - Width, - Height, - 0, - 0, - Width, - Height, - ClearBufferMask.ColorBufferBit, - BlitFramebufferFilter.Linear); - } - SDL_GL_SwapWindow(WindowHandle); } } diff --git a/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs b/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs index 9ec1dc63..0fcf517b 100644 --- a/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs +++ b/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs @@ -77,6 +77,6 @@ namespace Ryujinx.Headless.SDL2.Vulkan Device.DisposeGpu(); } - protected override void SwapBuffers(object texture) { } + protected override void SwapBuffers() { } } } diff --git a/Ryujinx.Headless.SDL2/WindowBase.cs b/Ryujinx.Headless.SDL2/WindowBase.cs index cc0986a0..2d6963dd 100644 --- a/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/Ryujinx.Headless.SDL2/WindowBase.cs @@ -157,7 +157,7 @@ namespace Ryujinx.Headless.SDL2 protected abstract void FinalizeWindowRenderer(); - protected abstract void SwapBuffers(object image); + protected abstract void SwapBuffers(); public abstract SDL_WindowFlags GetWindowFlags(); @@ -202,7 +202,7 @@ namespace Ryujinx.Headless.SDL2 while (Device.ConsumeFrameAvailable()) { - Device.PresentFrame((texture) => { SwapBuffers(texture); }); + Device.PresentFrame(SwapBuffers); } if (_ticks >= _ticksPerFrame) diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs index b839e9cd..06d414ed 100644 --- a/Ryujinx/Ui/GLRenderer.cs +++ b/Ryujinx/Ui/GLRenderer.cs @@ -97,31 +97,11 @@ namespace Ryujinx.Ui GL.ClearColor(0, 0, 0, 1.0f); GL.Clear(ClearBufferMask.ColorBufferBit); - SwapBuffers(0); + SwapBuffers(); } - public override void SwapBuffers(object image) + public override void SwapBuffers() { - if((int)image != 0) - { - // The game's framebruffer is already bound, so blit it to the window's backbuffer - GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0); - - GL.Clear(ClearBufferMask.ColorBufferBit); - GL.ClearColor(0, 0, 0, 1); - - GL.BlitFramebuffer(0, - 0, - WindowWidth, - WindowHeight, - 0, - 0, - WindowWidth, - WindowHeight, - ClearBufferMask.ColorBufferBit, - BlitFramebufferFilter.Linear); - } - _nativeWindow.SwapBuffers(); } diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs index 3cdc424e..6a728a26 100644 --- a/Ryujinx/Ui/RendererWidgetBase.cs +++ b/Ryujinx/Ui/RendererWidgetBase.cs @@ -119,7 +119,7 @@ namespace Ryujinx.Ui public abstract void InitializeRenderer(); - public abstract void SwapBuffers(object image); + public abstract void SwapBuffers(); protected abstract string GetGpuBackendName(); @@ -426,7 +426,7 @@ namespace Ryujinx.Ui while (Device.ConsumeFrameAvailable()) { - Device.PresentFrame((texture) => { SwapBuffers(texture);}); + Device.PresentFrame(SwapBuffers); } if (_ticks >= _ticksPerFrame) diff --git a/Ryujinx/Ui/VKRenderer.cs b/Ryujinx/Ui/VKRenderer.cs index f65b330b..49578d2a 100644 --- a/Ryujinx/Ui/VKRenderer.cs +++ b/Ryujinx/Ui/VKRenderer.cs @@ -63,7 +63,7 @@ namespace Ryujinx.Ui public override void InitializeRenderer() { } - public override void SwapBuffers(object image) { } + public override void SwapBuffers() { } protected override string GetGpuBackendName() {