Avalonia - Use embedded window for avalonia (#3674)
* wip * use embedded window * fix race condition on opengl Windows * fix glx issues on prime nvidia * fix mouse support win32 * clean up * addressed review * addressed review * fix warnings * fix sotware keyboard dialog * Update Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs Co-authored-by: gdkchan <gab.dark.100@gmail.com> * remove double semi Co-authored-by: gdkchan <gab.dark.100@gmail.com>
This commit is contained in:
parent
b9f1ff3c77
commit
6f0395538b
58 changed files with 868 additions and 3531 deletions
|
@ -1,6 +1,5 @@
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using ARMeilleure.Translation.PTC;
|
using ARMeilleure.Translation.PTC;
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
|
@ -12,10 +11,8 @@ using Ryujinx.Audio.Integration;
|
||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Input;
|
using Ryujinx.Ava.Input;
|
||||||
using Ryujinx.Ava.Ui.Backend.Vulkan;
|
|
||||||
using Ryujinx.Ava.Ui.Controls;
|
using Ryujinx.Ava.Ui.Controls;
|
||||||
using Ryujinx.Ava.Ui.Models;
|
using Ryujinx.Ava.Ui.Models;
|
||||||
using Ryujinx.Ava.Ui.Vulkan;
|
|
||||||
using Ryujinx.Ava.Ui.Windows;
|
using Ryujinx.Ava.Ui.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
@ -39,6 +36,7 @@ using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Png;
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
|
using SPB.Graphics.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -58,24 +56,24 @@ namespace Ryujinx.Ava
|
||||||
{
|
{
|
||||||
private const int CursorHideIdleTime = 8; // Hide Cursor seconds
|
private const int CursorHideIdleTime = 8; // Hide Cursor seconds
|
||||||
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
|
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 AccountManager _accountManager;
|
||||||
private readonly UserChannelPersistence _userChannelPersistence;
|
private readonly UserChannelPersistence _userChannelPersistence;
|
||||||
|
|
||||||
private readonly InputManager _inputManager;
|
private readonly InputManager _inputManager;
|
||||||
|
|
||||||
private readonly IKeyboard _keyboardInterface;
|
|
||||||
|
|
||||||
private readonly MainWindow _parent;
|
private readonly MainWindow _parent;
|
||||||
|
private readonly IKeyboard _keyboardInterface;
|
||||||
private readonly GraphicsDebugLevel _glLogLevel;
|
private readonly GraphicsDebugLevel _glLogLevel;
|
||||||
|
|
||||||
private bool _hideCursorOnIdle;
|
private bool _hideCursorOnIdle;
|
||||||
private bool _isStopped;
|
private bool _isStopped;
|
||||||
private bool _isActive;
|
private bool _isActive;
|
||||||
private long _lastCursorMoveTime;
|
private long _lastCursorMoveTime;
|
||||||
|
private long _ticks = 0;
|
||||||
|
|
||||||
private KeyboardHotkeyState _prevHotkeyState;
|
private KeyboardHotkeyState _prevHotkeyState;
|
||||||
|
|
||||||
|
@ -93,7 +91,7 @@ namespace Ryujinx.Ava
|
||||||
public event EventHandler AppExit;
|
public event EventHandler AppExit;
|
||||||
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
||||||
|
|
||||||
public RendererControl Renderer { get; }
|
public RendererHost Renderer { get; }
|
||||||
public VirtualFileSystem VirtualFileSystem { get; }
|
public VirtualFileSystem VirtualFileSystem { get; }
|
||||||
public ContentManager ContentManager { get; }
|
public ContentManager ContentManager { get; }
|
||||||
public Switch Device { get; set; }
|
public Switch Device { get; set; }
|
||||||
|
@ -111,7 +109,7 @@ namespace Ryujinx.Ava
|
||||||
private object _lockObject = new();
|
private object _lockObject = new();
|
||||||
|
|
||||||
public AppHost(
|
public AppHost(
|
||||||
RendererControl renderer,
|
RendererHost renderer,
|
||||||
InputManager inputManager,
|
InputManager inputManager,
|
||||||
string applicationPath,
|
string applicationPath,
|
||||||
VirtualFileSystem virtualFileSystem,
|
VirtualFileSystem virtualFileSystem,
|
||||||
|
@ -128,7 +126,7 @@ namespace Ryujinx.Ava
|
||||||
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
|
_hideCursorOnIdle = ConfigurationState.Instance.HideCursorOnIdle;
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
||||||
_inputManager.SetMouseDriver(new AvaloniaMouseDriver(renderer));
|
_inputManager.SetMouseDriver(new AvaloniaMouseDriver(_parent, renderer));
|
||||||
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
|
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
|
||||||
|
|
||||||
NpadManager = _inputManager.CreateNpadManager();
|
NpadManager = _inputManager.CreateNpadManager();
|
||||||
|
@ -138,6 +136,9 @@ namespace Ryujinx.Ava
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
ContentManager = contentManager;
|
ContentManager = contentManager;
|
||||||
|
|
||||||
|
_chrono = new Stopwatch();
|
||||||
|
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
||||||
|
|
||||||
if (ApplicationPath.StartsWith("@SystemContent"))
|
if (ApplicationPath.StartsWith("@SystemContent"))
|
||||||
{
|
{
|
||||||
ApplicationPath = _parent.VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath);
|
ApplicationPath = _parent.VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath);
|
||||||
|
@ -177,7 +178,7 @@ namespace Ryujinx.Ava
|
||||||
if (_renderer != null)
|
if (_renderer != null)
|
||||||
{
|
{
|
||||||
double scale = _parent.PlatformImpl.RenderScaling;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AvaloniaLocator.Current.GetService<VulkanPlatformInterface>()?.MainSurface.Display.ChangeVSyncMode(true);
|
|
||||||
|
|
||||||
_isStopped = true;
|
_isStopped = true;
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
}
|
}
|
||||||
|
@ -376,6 +375,8 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
_gpuCancellationTokenSource.Cancel();
|
_gpuCancellationTokenSource.Cancel();
|
||||||
_gpuCancellationTokenSource.Dispose();
|
_gpuCancellationTokenSource.Dispose();
|
||||||
|
|
||||||
|
_chrono.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisposeGpu()
|
public void DisposeGpu()
|
||||||
|
@ -389,8 +390,7 @@ namespace Ryujinx.Ava
|
||||||
Renderer?.MakeCurrent();
|
Renderer?.MakeCurrent();
|
||||||
|
|
||||||
Device.DisposeGpu();
|
Device.DisposeGpu();
|
||||||
|
|
||||||
Renderer?.DestroyBackgroundContext();
|
|
||||||
Renderer?.MakeCurrent(null);
|
Renderer?.MakeCurrent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -596,16 +596,11 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
IRenderer renderer;
|
IRenderer renderer;
|
||||||
|
|
||||||
if (Program.UseVulkan)
|
if (Renderer.IsVulkan)
|
||||||
{
|
{
|
||||||
var vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value;
|
||||||
|
|
||||||
renderer = new VulkanRenderer(vulkan.Instance.InternalHandle,
|
renderer = new VulkanRenderer(Renderer.CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu);
|
||||||
vulkan.MainSurface.Device.InternalHandle,
|
|
||||||
vulkan.PhysicalDevice.InternalHandle,
|
|
||||||
vulkan.MainSurface.Device.Queue.InternalHandle,
|
|
||||||
vulkan.PhysicalDevice.QueueFamilyIndex,
|
|
||||||
vulkan.MainSurface.Device.Lock);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -778,11 +773,7 @@ namespace Ryujinx.Ava
|
||||||
{
|
{
|
||||||
Width = (int)e.Width;
|
Width = (int)e.Width;
|
||||||
Height = (int)e.Height;
|
Height = (int)e.Height;
|
||||||
|
SetRendererWindowSize(e);
|
||||||
if (!Program.UseVulkan)
|
|
||||||
{
|
|
||||||
SetRendererWindowSize(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MainLoop()
|
private void MainLoop()
|
||||||
|
@ -822,12 +813,10 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
||||||
|
|
||||||
(_renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext((Renderer as OpenGLRendererControl).GameContext));
|
(_renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Renderer.GetContext()));
|
||||||
|
|
||||||
Renderer.MakeCurrent();
|
Renderer.MakeCurrent();
|
||||||
|
|
||||||
AvaloniaLocator.Current.GetService<VulkanPlatformInterface>()?.MainSurface?.Display?.ChangeVSyncMode(Device.EnableDeviceVsync);
|
|
||||||
|
|
||||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||||
|
|
||||||
Width = (int)Renderer.Bounds.Width;
|
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));
|
_renderer.Window.SetSize((int)(Width * _parent.PlatformImpl.RenderScaling), (int)(Height * _parent.PlatformImpl.RenderScaling));
|
||||||
|
|
||||||
|
_chrono.Start();
|
||||||
|
|
||||||
Device.Gpu.Renderer.RunLoop(() =>
|
Device.Gpu.Renderer.RunLoop(() =>
|
||||||
{
|
{
|
||||||
Device.Gpu.SetGpuThread();
|
Device.Gpu.SetGpuThread();
|
||||||
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
||||||
Translator.IsReadyForTranslation.Set();
|
Translator.IsReadyForTranslation.Set();
|
||||||
|
|
||||||
Renderer.Start();
|
|
||||||
|
|
||||||
while (_isActive)
|
while (_isActive)
|
||||||
{
|
{
|
||||||
|
_ticks += _chrono.ElapsedTicks;
|
||||||
|
|
||||||
|
_chrono.Restart();
|
||||||
|
|
||||||
if (Device.WaitFifo())
|
if (Device.WaitFifo())
|
||||||
{
|
{
|
||||||
Device.Statistics.RecordFifoStart();
|
Device.Statistics.RecordFifoStart();
|
||||||
|
@ -860,19 +853,20 @@ namespace Ryujinx.Ava
|
||||||
_parent.SwitchToGameControl();
|
_parent.SwitchToGameControl();
|
||||||
}
|
}
|
||||||
|
|
||||||
Device.PresentFrame(Present);
|
Device.PresentFrame(() => Renderer?.SwapBuffers());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_ticks >= _ticksPerFrame)
|
||||||
|
{
|
||||||
|
UpdateStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer.Stop();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Renderer?.MakeCurrent(null);
|
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
|
// 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"];
|
string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance["Docked"] : LocaleManager.Instance["Handheld"];
|
||||||
|
@ -886,24 +880,12 @@ namespace Ryujinx.Ava
|
||||||
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
||||||
Device.EnableDeviceVsync,
|
Device.EnableDeviceVsync,
|
||||||
Device.GetVolume(),
|
Device.GetVolume(),
|
||||||
Program.UseVulkan ? "Vulkan" : "OpenGL",
|
Renderer.IsVulkan ? "Vulkan" : "OpenGL",
|
||||||
dockedMode,
|
dockedMode,
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
|
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
|
||||||
LocaleManager.Instance["Game"] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
|
LocaleManager.Instance["Game"] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
|
||||||
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
|
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
|
||||||
$"GPU: {_renderer.GetHardwareInfo().GpuVendor}"));
|
$"GPU: {_renderer.GetHardwareInfo().GpuVendor}"));
|
||||||
|
|
||||||
if (Program.UseVulkan)
|
|
||||||
{
|
|
||||||
var platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
|
||||||
if (platformInterface.MainSurface.Display.IsSurfaceChanged())
|
|
||||||
{
|
|
||||||
SetRendererWindowSize(new Size(Width, Height));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Renderer.Present(image);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ShowExitPrompt()
|
public async Task ShowExitPrompt()
|
||||||
|
@ -985,8 +967,6 @@ namespace Ryujinx.Ava
|
||||||
case KeyboardHotkeyState.ToggleVSync:
|
case KeyboardHotkeyState.ToggleVSync:
|
||||||
Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
|
Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
|
||||||
|
|
||||||
AvaloniaLocator.Current.GetService<VulkanPlatformInterface>()?.MainSurface?.Display?.ChangeVSyncMode(Device.EnableDeviceVsync);
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case KeyboardHotkeyState.Screenshot:
|
case KeyboardHotkeyState.Screenshot:
|
||||||
ScreenshotRequested = true;
|
ScreenshotRequested = true;
|
||||||
|
|
|
@ -14,20 +14,27 @@ namespace Ryujinx.Ava.Input
|
||||||
private Control _widget;
|
private Control _widget;
|
||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
private Size _size;
|
private Size _size;
|
||||||
|
private readonly Window _window;
|
||||||
|
|
||||||
public bool[] PressedButtons { get; }
|
public bool[] PressedButtons { get; }
|
||||||
|
|
||||||
public Vector2 CurrentPosition { get; private set; }
|
public Vector2 CurrentPosition { get; private set; }
|
||||||
public Vector2 Scroll { get; private set; }
|
public Vector2 Scroll { get; private set; }
|
||||||
|
|
||||||
public AvaloniaMouseDriver(Control parent)
|
public AvaloniaMouseDriver(Window window, Control parent)
|
||||||
{
|
{
|
||||||
_widget = parent;
|
_widget = parent;
|
||||||
|
_window = window;
|
||||||
|
|
||||||
_widget.PointerMoved += Parent_PointerMovedEvent;
|
_widget.PointerMoved += Parent_PointerMovedEvent;
|
||||||
_widget.PointerPressed += Parent_PointerPressEvent;
|
_widget.PointerPressed += Parent_PointerPressEvent;
|
||||||
_widget.PointerReleased += Parent_PointerReleaseEvent;
|
_widget.PointerReleased += Parent_PointerReleaseEvent;
|
||||||
_widget.PointerWheelChanged += Parent_ScrollEvent;
|
_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];
|
PressedButtons = new bool[(int)MouseButton.Count];
|
||||||
|
|
||||||
|
@ -47,7 +54,6 @@ namespace Ryujinx.Ava.Input
|
||||||
|
|
||||||
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
|
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
|
||||||
{
|
{
|
||||||
var pointerProperties = args.GetCurrentPoint(_widget).Properties;
|
|
||||||
PressedButtons[(int)args.InitialPressMouseButton - 1] = false;
|
PressedButtons[(int)args.InitialPressMouseButton - 1] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +131,11 @@ namespace Ryujinx.Ava.Input
|
||||||
_widget.PointerReleased -= Parent_PointerReleaseEvent;
|
_widget.PointerReleased -= Parent_PointerReleaseEvent;
|
||||||
_widget.PointerWheelChanged -= Parent_ScrollEvent;
|
_widget.PointerWheelChanged -= Parent_ScrollEvent;
|
||||||
|
|
||||||
|
_window.PointerMoved -= Parent_PointerMovedEvent;
|
||||||
|
_window.PointerPressed -= Parent_PointerPressEvent;
|
||||||
|
_window.PointerReleased -= Parent_PointerReleaseEvent;
|
||||||
|
_window.PointerWheelChanged -= Parent_ScrollEvent;
|
||||||
|
|
||||||
_widget = null;
|
_widget = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
using ARMeilleure.Translation.PTC;
|
using ARMeilleure.Translation.PTC;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.OpenGL;
|
|
||||||
using Avalonia.Rendering;
|
using Avalonia.Rendering;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Ryujinx.Ava.Ui.Backend;
|
|
||||||
using Ryujinx.Ava.Ui.Controls;
|
using Ryujinx.Ava.Ui.Controls;
|
||||||
using Ryujinx.Ava.Ui.Windows;
|
using Ryujinx.Ava.Ui.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
@ -12,12 +10,10 @@ using Ryujinx.Common.GraphicsDriver;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.System;
|
using Ryujinx.Common.System;
|
||||||
using Ryujinx.Common.SystemInfo;
|
using Ryujinx.Common.SystemInfo;
|
||||||
using Ryujinx.Graphics.Vulkan;
|
|
||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
using Ryujinx.Ui.Common;
|
using Ryujinx.Ui.Common;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -34,7 +30,6 @@ namespace Ryujinx.Ava
|
||||||
public static bool PreviewerDetached { get; private set; }
|
public static bool PreviewerDetached { get; private set; }
|
||||||
|
|
||||||
public static RenderTimer RenderTimer { get; private set; }
|
public static RenderTimer RenderTimer { get; private set; }
|
||||||
public static bool UseVulkan { get; private set; }
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
|
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
|
||||||
|
@ -71,36 +66,16 @@ namespace Ryujinx.Ava
|
||||||
EnableMultiTouch = true,
|
EnableMultiTouch = true,
|
||||||
EnableIme = true,
|
EnableIme = true,
|
||||||
UseEGL = false,
|
UseEGL = false,
|
||||||
UseGpu = !UseVulkan,
|
UseGpu = false
|
||||||
GlProfiles = new List<GlVersion>()
|
|
||||||
{
|
|
||||||
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.With(new Win32PlatformOptions
|
.With(new Win32PlatformOptions
|
||||||
{
|
{
|
||||||
EnableMultitouch = true,
|
EnableMultitouch = true,
|
||||||
UseWgl = !UseVulkan,
|
UseWgl = false,
|
||||||
WglProfiles = new List<GlVersion>()
|
|
||||||
{
|
|
||||||
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
|
||||||
},
|
|
||||||
AllowEglInitialization = false,
|
AllowEglInitialization = false,
|
||||||
CompositionBackdropCornerRadius = 8f,
|
CompositionBackdropCornerRadius = 8f,
|
||||||
})
|
})
|
||||||
.UseSkia()
|
.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(_ =>
|
.AfterSetup(_ =>
|
||||||
{
|
{
|
||||||
AvaloniaLocator.CurrentMutable
|
AvaloniaLocator.CurrentMutable
|
||||||
|
@ -176,26 +151,7 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
ReloadConfig();
|
ReloadConfig();
|
||||||
|
|
||||||
UseVulkan = PreviewerDetached ? ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan : false;
|
ForceDpiAware.Windows();
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
||||||
ActualScaleFactor = ForceDpiAware.GetActualScaleFactor() / BaseDpi;
|
ActualScaleFactor = ForceDpiAware.GetActualScaleFactor() / BaseDpi;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
|
@ -59,29 +60,63 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
|
||||||
string input = string.Empty;
|
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);
|
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<ContentDialog, ContentDialogClosedEventArgs> handler = (sender, eventArgs) =>
|
||||||
{
|
{
|
||||||
content._host = contentDialog;
|
if (eventArgs.Result == ContentDialogResult.Primary)
|
||||||
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<ContentDialog, ContentDialogClosedEventArgs> handler = (sender, eventArgs) =>
|
|
||||||
{
|
{
|
||||||
if (eventArgs.Result == ContentDialogResult.Primary)
|
result = UserResult.Ok;
|
||||||
{
|
input = content.Input.Text;
|
||||||
result = UserResult.Ok;
|
}
|
||||||
input = content.Input.Text;
|
};
|
||||||
}
|
contentDialog.Closed += handler;
|
||||||
};
|
|
||||||
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();
|
await contentDialog.ShowAsync();
|
||||||
contentDialog.Closed -= handler;
|
contentDialog.Closed -= handler;
|
||||||
}
|
overlay.Close();
|
||||||
|
};
|
||||||
|
|
||||||
|
await overlay.ShowDialog(window);
|
||||||
|
|
||||||
return (result, input);
|
return (result, input);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<SkiaOptions>() ?? new SkiaOptions();
|
|
||||||
var platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
|
||||||
|
|
||||||
if (platformInterface == null)
|
|
||||||
{
|
|
||||||
VulkanPlatformInterface.TryInitialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
var gpu = new VulkanSkiaGpu(skiaOptions.MaxGpuResourceSizeBytes);
|
|
||||||
AvaloniaLocator.CurrentMutable.Bind<VulkanSkiaGpu>().ToConstant(gpu);
|
|
||||||
|
|
||||||
return gpu;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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}\".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<VulkanSkiaGpu>();
|
|
||||||
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<VulkanPlatformInterface>();
|
|
||||||
MaxResourceBytes = maxResourceBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<VulkanCommandBuffer> _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<AllocationCallbacks>.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<Semaphore> waitSemaphores,
|
|
||||||
ReadOnlySpan<PipelineStageFlags> waitDstStageMask,
|
|
||||||
ReadOnlySpan<Semaphore> 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<AllocationCallbacks>.Empty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<AllocationCallbacks>.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<ImageView>();
|
|
||||||
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<AllocationCallbacks>.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<AllocationCallbacks>.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<AllocationCallbacks>.Empty);
|
|
||||||
_device.Api.DestroyImage(_device.InternalHandle, InternalHandle.Value, Span<AllocationCallbacks>.Empty);
|
|
||||||
_device.Api.FreeMemory(_device.InternalHandle, _imageMemory, Span<AllocationCallbacks>.Empty);
|
|
||||||
|
|
||||||
_imageView = default;
|
|
||||||
InternalHandle = null;
|
|
||||||
_imageMemory = default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<string> 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<AllocationCallbacks>.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<string>(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<string>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Vulkan
|
|
||||||
{
|
|
||||||
public class VulkanOptions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the application name of the Vulkan instance
|
|
||||||
/// </summary>
|
|
||||||
public string ApplicationName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Specifies additional extensions to enable if available on the instance
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<string> InstanceExtensions { get; set; } = Enumerable.Empty<string>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Specifies layers to enable if available on the instance
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<string> EnabledLayers { get; set; } = Enumerable.Empty<string>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enables the debug layer
|
|
||||||
/// </summary>
|
|
||||||
public bool UseDebug { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Selects the first suitable discrete GPU available
|
|
||||||
/// </summary>
|
|
||||||
public bool PreferDiscreteGpu { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the device to use if available and suitable.
|
|
||||||
/// </summary>
|
|
||||||
public string PreferredDevice { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Max number of device queues to request
|
|
||||||
/// </summary>
|
|
||||||
public uint MaxQueueCount { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<PhysicalDevice, PhysicalDeviceProperties> PhysicalDevices { get; private set; }
|
|
||||||
public static IEnumerable<KeyValuePair<PhysicalDevice, PhysicalDeviceProperties>> 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<PhysicalDevice, PhysicalDeviceProperties>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<VulkanOptions>() ?? new VulkanOptions();
|
|
||||||
|
|
||||||
var instance = VulkanInstance.Create(_options);
|
|
||||||
|
|
||||||
return new VulkanPlatformInterface(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryInitialize()
|
|
||||||
{
|
|
||||||
var feature = TryCreate();
|
|
||||||
if (feature != null)
|
|
||||||
{
|
|
||||||
AvaloniaLocator.CurrentMutable.Bind<VulkanPlatformInterface>().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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<AllocationCallbacks>.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<uint> surfaceFormatsCount = stackalloc uint[1];
|
|
||||||
SurfaceExtension.GetPhysicalDeviceSurfaceFormats(physicalDevice.InternalHandle, ApiHandle, surfaceFormatsCount, Span<SurfaceFormatKHR>.Empty);
|
|
||||||
Span<SurfaceFormatKHR> 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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Media;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
@ -27,9 +31,69 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
{
|
{
|
||||||
UserResult result = UserResult.None;
|
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()
|
async Task ShowDialog()
|
||||||
{
|
{
|
||||||
|
@ -53,6 +117,14 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
});
|
});
|
||||||
|
|
||||||
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
|
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
|
||||||
|
|
||||||
|
overlay?.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useOverlay)
|
||||||
|
{
|
||||||
|
overlay.Content = null;
|
||||||
|
overlay.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -323,4 +395,4 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
204
Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs
Normal file
204
Ryujinx.Ava/Ui/Controls/EmbeddedWindow.cs
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using SPB.Graphics;
|
||||||
|
using SPB.Platform;
|
||||||
|
using SPB.Platform.GLX;
|
||||||
|
using SPB.Platform.X11;
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static Ryujinx.Ava.Ui.Controls.Win32NativeInterop;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
{
|
||||||
|
public unsafe class EmbeddedWindow : NativeControlHost
|
||||||
|
{
|
||||||
|
private WindowProc _wndProcDelegate;
|
||||||
|
private string _className;
|
||||||
|
|
||||||
|
protected GLXWindow X11Window { get; private set; }
|
||||||
|
protected IntPtr WindowHandle { get; set; }
|
||||||
|
protected IntPtr X11Display { get; set; }
|
||||||
|
|
||||||
|
public event EventHandler<IntPtr> WindowCreated;
|
||||||
|
public event EventHandler<Size> SizeChanged;
|
||||||
|
|
||||||
|
protected virtual void OnWindowDestroyed() { }
|
||||||
|
protected virtual void OnWindowDestroying()
|
||||||
|
{
|
||||||
|
WindowHandle = IntPtr.Zero;
|
||||||
|
X11Display = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmbeddedWindow()
|
||||||
|
{
|
||||||
|
var stateObserverable = this.GetObservable(Control.BoundsProperty);
|
||||||
|
|
||||||
|
stateObserverable.Subscribe(StateChanged);
|
||||||
|
|
||||||
|
this.Initialized += NativeEmbeddedWindow_Initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnWindowCreated() { }
|
||||||
|
|
||||||
|
private void NativeEmbeddedWindow_Initialized(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
OnWindowCreated();
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
WindowCreated?.Invoke(this, WindowHandle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StateChanged(Rect rect)
|
||||||
|
{
|
||||||
|
SizeChanged?.Invoke(this, rect.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
return CreateLinux(parent);
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return CreateWin32(parent);
|
||||||
|
}
|
||||||
|
return base.CreateNativeControlCore(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
||||||
|
{
|
||||||
|
OnWindowDestroying();
|
||||||
|
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
DestroyLinux();
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
DestroyWin32(control);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base.DestroyNativeControlCore(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnWindowDestroyed();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("linux")]
|
||||||
|
IPlatformHandle CreateLinux(IPlatformHandle parent)
|
||||||
|
{
|
||||||
|
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
|
||||||
|
|
||||||
|
WindowHandle = X11Window.WindowHandle.RawHandle;
|
||||||
|
|
||||||
|
X11Display = X11Window.DisplayHandle.RawHandle;
|
||||||
|
|
||||||
|
return new PlatformHandle(WindowHandle, "X11");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
unsafe IPlatformHandle CreateWin32(IPlatformHandle parent)
|
||||||
|
{
|
||||||
|
_className = "NativeWindow-" + Guid.NewGuid();
|
||||||
|
_wndProcDelegate = WndProc;
|
||||||
|
var wndClassEx = new WNDCLASSEX
|
||||||
|
{
|
||||||
|
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
|
||||||
|
hInstance = GetModuleHandle(null),
|
||||||
|
lpfnWndProc = _wndProcDelegate,
|
||||||
|
style = ClassStyles.CS_OWNDC,
|
||||||
|
lpszClassName = _className,
|
||||||
|
hCursor = LoadCursor(IntPtr.Zero, (IntPtr)Cursors.IDC_ARROW)
|
||||||
|
};
|
||||||
|
|
||||||
|
var atom = RegisterClassEx(ref wndClassEx);
|
||||||
|
|
||||||
|
var handle = CreateWindowEx(
|
||||||
|
0,
|
||||||
|
_className,
|
||||||
|
"NativeWindow",
|
||||||
|
WindowStyles.WS_CHILD,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
640,
|
||||||
|
480,
|
||||||
|
parent.Handle,
|
||||||
|
IntPtr.Zero,
|
||||||
|
IntPtr.Zero,
|
||||||
|
IntPtr.Zero);
|
||||||
|
|
||||||
|
WindowHandle = handle;
|
||||||
|
|
||||||
|
return new PlatformHandle(WindowHandle, "HWND");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
internal IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
|
||||||
|
{
|
||||||
|
var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF);
|
||||||
|
var root = VisualRoot as Window;
|
||||||
|
bool isLeft = false;
|
||||||
|
switch (msg)
|
||||||
|
{
|
||||||
|
case WindowsMessages.LBUTTONDOWN:
|
||||||
|
case WindowsMessages.RBUTTONDOWN:
|
||||||
|
isLeft = msg == WindowsMessages.LBUTTONDOWN;
|
||||||
|
this.RaiseEvent(new PointerPressedEventArgs(
|
||||||
|
this,
|
||||||
|
new Avalonia.Input.Pointer(0, PointerType.Mouse, true),
|
||||||
|
root,
|
||||||
|
this.TranslatePoint(point, root).Value,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed),
|
||||||
|
KeyModifiers.None));
|
||||||
|
break;
|
||||||
|
case WindowsMessages.LBUTTONUP:
|
||||||
|
case WindowsMessages.RBUTTONUP:
|
||||||
|
isLeft = msg == WindowsMessages.LBUTTONUP;
|
||||||
|
this.RaiseEvent(new PointerReleasedEventArgs(
|
||||||
|
this,
|
||||||
|
new Avalonia.Input.Pointer(0, PointerType.Mouse, true),
|
||||||
|
root,
|
||||||
|
this.TranslatePoint(point, root).Value,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased),
|
||||||
|
KeyModifiers.None,
|
||||||
|
isLeft ? MouseButton.Left : MouseButton.Right));
|
||||||
|
break;
|
||||||
|
case WindowsMessages.MOUSEMOVE:
|
||||||
|
this.RaiseEvent(new PointerEventArgs(
|
||||||
|
PointerMovedEvent,
|
||||||
|
this,
|
||||||
|
new Avalonia.Input.Pointer(0, PointerType.Mouse, true),
|
||||||
|
root,
|
||||||
|
this.TranslatePoint(point, root).Value,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
|
||||||
|
KeyModifiers.None));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return DefWindowProc(hWnd, msg, (IntPtr)wParam, (IntPtr)lParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyLinux()
|
||||||
|
{
|
||||||
|
X11Window?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
void DestroyWin32(IPlatformHandle handle)
|
||||||
|
{
|
||||||
|
DestroyWindow(handle.Handle);
|
||||||
|
UnregisterClass(_className, GetModuleHandle(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
Ryujinx.Ava/Ui/Controls/OpenGLEmbeddedWindow.cs
Normal file
85
Ryujinx.Ava/Ui/Controls/OpenGLEmbeddedWindow.cs
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,192 +0,0 @@
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.OpenGL;
|
|
||||||
using Avalonia.Platform;
|
|
||||||
using Avalonia.Rendering.SceneGraph;
|
|
||||||
using Avalonia.Skia;
|
|
||||||
using Avalonia.Threading;
|
|
||||||
using OpenTK.Graphics.OpenGL;
|
|
||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using SkiaSharp;
|
|
||||||
using SPB.Graphics;
|
|
||||||
using SPB.Graphics.OpenGL;
|
|
||||||
using SPB.Platform;
|
|
||||||
using SPB.Windowing;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Controls
|
|
||||||
{
|
|
||||||
internal class OpenGLRendererControl : RendererControl
|
|
||||||
{
|
|
||||||
public int Major { get; }
|
|
||||||
public int Minor { get; }
|
|
||||||
public OpenGLContextBase GameContext { get; set; }
|
|
||||||
|
|
||||||
public static OpenGLContextBase PrimaryContext =>
|
|
||||||
AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>()
|
|
||||||
.PrimaryContext.AsOpenGLContextBase();
|
|
||||||
|
|
||||||
private SwappableNativeWindowBase _gameBackgroundWindow;
|
|
||||||
|
|
||||||
private IntPtr _fence;
|
|
||||||
|
|
||||||
public OpenGLRendererControl(int major, int minor, GraphicsDebugLevel graphicsDebugLevel) : base(graphicsDebugLevel)
|
|
||||||
{
|
|
||||||
Major = major;
|
|
||||||
Minor = minor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void DestroyBackgroundContext()
|
|
||||||
{
|
|
||||||
Image = null;
|
|
||||||
|
|
||||||
if (_fence != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
DrawOperation.Dispose();
|
|
||||||
GL.DeleteSync(_fence);
|
|
||||||
}
|
|
||||||
|
|
||||||
GlDrawOperation.DeleteFramebuffer();
|
|
||||||
|
|
||||||
GameContext?.Dispose();
|
|
||||||
|
|
||||||
_gameBackgroundWindow?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Present(object image)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
Image = (int)image;
|
|
||||||
|
|
||||||
InvalidateVisual();
|
|
||||||
}).Wait();
|
|
||||||
|
|
||||||
if (_fence != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
GL.DeleteSync(_fence);
|
|
||||||
}
|
|
||||||
|
|
||||||
_fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
|
|
||||||
|
|
||||||
InvalidateVisual();
|
|
||||||
|
|
||||||
_gameBackgroundWindow.SwapBuffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void MakeCurrent()
|
|
||||||
{
|
|
||||||
GameContext.MakeCurrent(_gameBackgroundWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void MakeCurrent(SwappableNativeWindowBase window)
|
|
||||||
{
|
|
||||||
GameContext.MakeCurrent(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void CreateWindow()
|
|
||||||
{
|
|
||||||
var flags = OpenGLContextFlags.Compat;
|
|
||||||
if (DebugLevel != GraphicsDebugLevel.None)
|
|
||||||
{
|
|
||||||
flags |= OpenGLContextFlags.Debug;
|
|
||||||
}
|
|
||||||
_gameBackgroundWindow = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100);
|
|
||||||
_gameBackgroundWindow.Hide();
|
|
||||||
|
|
||||||
GameContext = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, Major, Minor, flags, shareContext: PrimaryContext);
|
|
||||||
GameContext.Initialize(_gameBackgroundWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ICustomDrawOperation CreateDrawOperation()
|
|
||||||
{
|
|
||||||
return new GlDrawOperation(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class GlDrawOperation : ICustomDrawOperation
|
|
||||||
{
|
|
||||||
private static int _framebuffer;
|
|
||||||
|
|
||||||
public Rect Bounds { get; }
|
|
||||||
|
|
||||||
private readonly OpenGLRendererControl _control;
|
|
||||||
|
|
||||||
public GlDrawOperation(OpenGLRendererControl control)
|
|
||||||
{
|
|
||||||
_control = control;
|
|
||||||
Bounds = _control.Bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() { }
|
|
||||||
|
|
||||||
public static void DeleteFramebuffer()
|
|
||||||
{
|
|
||||||
if (_framebuffer == 0)
|
|
||||||
{
|
|
||||||
GL.DeleteFramebuffer(_framebuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
_framebuffer = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(ICustomDrawOperation other)
|
|
||||||
{
|
|
||||||
return other is GlDrawOperation operation && Equals(this, operation) && operation.Bounds == Bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HitTest(Point p)
|
|
||||||
{
|
|
||||||
return Bounds.Contains(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateRenderTarget()
|
|
||||||
{
|
|
||||||
_framebuffer = GL.GenFramebuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Render(IDrawingContextImpl context)
|
|
||||||
{
|
|
||||||
if (_control.Image == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_framebuffer == 0)
|
|
||||||
{
|
|
||||||
CreateRenderTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
int currentFramebuffer = GL.GetInteger(GetPName.FramebufferBinding);
|
|
||||||
|
|
||||||
var image = _control.Image;
|
|
||||||
var fence = _control._fence;
|
|
||||||
|
|
||||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, _framebuffer);
|
|
||||||
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, (int)image, 0);
|
|
||||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, currentFramebuffer);
|
|
||||||
|
|
||||||
if (context is not ISkiaDrawingContextImpl skiaDrawingContextImpl)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var imageInfo = new SKImageInfo((int)_control.RenderSize.Width, (int)_control.RenderSize.Height, SKColorType.Rgba8888);
|
|
||||||
var glInfo = new GRGlFramebufferInfo((uint)_framebuffer, SKColorType.Rgba8888.ToGlSizedFormat());
|
|
||||||
|
|
||||||
GL.WaitSync(fence, WaitSyncFlags.None, ulong.MaxValue);
|
|
||||||
|
|
||||||
using var backendTexture = new GRBackendRenderTarget(imageInfo.Width, imageInfo.Height, 1, 0, glInfo);
|
|
||||||
using var surface = SKSurface.Create(skiaDrawingContextImpl.GrContext, backendTexture, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888);
|
|
||||||
|
|
||||||
if (surface == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rect = new Rect(new Point(), _control.RenderSize);
|
|
||||||
|
|
||||||
using var snapshot = surface.Snapshot();
|
|
||||||
skiaDrawingContextImpl.SkCanvas.DrawImage(snapshot, rect.ToSKRect(), _control.Bounds.ToSKRect(), new SKPaint());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Data;
|
|
||||||
using Avalonia.Media;
|
|
||||||
using Avalonia.Rendering.SceneGraph;
|
|
||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using SPB.Windowing;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Controls
|
|
||||||
{
|
|
||||||
internal abstract class RendererControl : Control
|
|
||||||
{
|
|
||||||
protected object Image { get; set; }
|
|
||||||
|
|
||||||
public event EventHandler<EventArgs> RendererInitialized;
|
|
||||||
public event EventHandler<Size> SizeChanged;
|
|
||||||
|
|
||||||
protected Size RenderSize { get; private set; }
|
|
||||||
public bool IsStarted { get; private set; }
|
|
||||||
|
|
||||||
public GraphicsDebugLevel DebugLevel { get; }
|
|
||||||
|
|
||||||
private bool _isInitialized;
|
|
||||||
|
|
||||||
protected ICustomDrawOperation DrawOperation { get; private set; }
|
|
||||||
|
|
||||||
public RendererControl(GraphicsDebugLevel graphicsDebugLevel)
|
|
||||||
{
|
|
||||||
DebugLevel = graphicsDebugLevel;
|
|
||||||
IObservable<Rect> resizeObservable = this.GetObservable(BoundsProperty);
|
|
||||||
|
|
||||||
resizeObservable.Subscribe(Resized);
|
|
||||||
|
|
||||||
Focusable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void Resized(Rect rect)
|
|
||||||
{
|
|
||||||
SizeChanged?.Invoke(this, rect.Size);
|
|
||||||
|
|
||||||
if (!rect.IsEmpty)
|
|
||||||
{
|
|
||||||
RenderSize = rect.Size * VisualRoot.RenderScaling;
|
|
||||||
DrawOperation = CreateDrawOperation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract ICustomDrawOperation CreateDrawOperation();
|
|
||||||
protected abstract void CreateWindow();
|
|
||||||
|
|
||||||
public override void Render(DrawingContext context)
|
|
||||||
{
|
|
||||||
if (!_isInitialized)
|
|
||||||
{
|
|
||||||
CreateWindow();
|
|
||||||
|
|
||||||
OnRendererInitialized();
|
|
||||||
_isInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsStarted || Image == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DrawOperation != null)
|
|
||||||
{
|
|
||||||
context.Custom(DrawOperation);
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Render(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void OnRendererInitialized()
|
|
||||||
{
|
|
||||||
RendererInitialized?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal abstract void Present(object image);
|
|
||||||
|
|
||||||
internal void Start()
|
|
||||||
{
|
|
||||||
IsStarted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Stop()
|
|
||||||
{
|
|
||||||
IsStarted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void DestroyBackgroundContext();
|
|
||||||
internal abstract void MakeCurrent();
|
|
||||||
internal abstract void MakeCurrent(SwappableNativeWindowBase window);
|
|
||||||
}
|
|
||||||
}
|
|
14
Ryujinx.Ava/Ui/Controls/RendererHost.axaml
Normal file
14
Ryujinx.Ava/Ui/Controls/RendererHost.axaml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Ryujinx.Ava.Ui.Controls.RendererHost">
|
||||||
|
<ContentControl
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Stretch"
|
||||||
|
Name="View"
|
||||||
|
/>
|
||||||
|
</UserControl>
|
126
Ryujinx.Ava/Ui/Controls/RendererHost.axaml.cs
Normal file
126
Ryujinx.Ava/Ui/Controls/RendererHost.axaml.cs
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using SPB.Graphics.OpenGL;
|
||||||
|
using SPB.Windowing;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
{
|
||||||
|
public partial class RendererHost : UserControl, IDisposable
|
||||||
|
{
|
||||||
|
private readonly GraphicsDebugLevel _graphicsDebugLevel;
|
||||||
|
private EmbeddedWindow _currentWindow;
|
||||||
|
|
||||||
|
public bool IsVulkan { get; private set; }
|
||||||
|
|
||||||
|
public RendererHost(GraphicsDebugLevel graphicsDebugLevel)
|
||||||
|
{
|
||||||
|
_graphicsDebugLevel = graphicsDebugLevel;
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RendererHost()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateOpenGL()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
|
||||||
|
_currentWindow = new OpenGLEmbeddedWindow(3, 3, _graphicsDebugLevel);
|
||||||
|
Initialize();
|
||||||
|
|
||||||
|
IsVulkan = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
_currentWindow.WindowCreated += CurrentWindow_WindowCreated;
|
||||||
|
_currentWindow.SizeChanged += CurrentWindow_SizeChanged;
|
||||||
|
View.Content = _currentWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateVulkan()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
|
||||||
|
_currentWindow = new VulkanEmbeddedWindow();
|
||||||
|
Initialize();
|
||||||
|
|
||||||
|
IsVulkan = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpenGLContextBase GetContext()
|
||||||
|
{
|
||||||
|
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||||
|
{
|
||||||
|
return openGlEmbeddedWindow.Context;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CurrentWindow_SizeChanged(object sender, Size e)
|
||||||
|
{
|
||||||
|
SizeChanged?.Invoke(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CurrentWindow_WindowCreated(object sender, IntPtr e)
|
||||||
|
{
|
||||||
|
RendererInitialized?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MakeCurrent()
|
||||||
|
{
|
||||||
|
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||||
|
{
|
||||||
|
openGlEmbeddedWindow.MakeCurrent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MakeCurrent(SwappableNativeWindowBase window)
|
||||||
|
{
|
||||||
|
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||||
|
{
|
||||||
|
openGlEmbeddedWindow.MakeCurrent(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SwapBuffers()
|
||||||
|
{
|
||||||
|
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
||||||
|
{
|
||||||
|
openGlEmbeddedWindow.SwapBuffers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs> RendererInitialized;
|
||||||
|
public event Action<object, Size> SizeChanged;
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_currentWindow != null)
|
||||||
|
{
|
||||||
|
_currentWindow.WindowCreated -= CurrentWindow_WindowCreated;
|
||||||
|
_currentWindow.SizeChanged -= CurrentWindow_SizeChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SurfaceKHR CreateVulkanSurface(Instance instance, Vk api)
|
||||||
|
{
|
||||||
|
return (_currentWindow is VulkanEmbeddedWindow vulkanEmbeddedWindow)
|
||||||
|
? vulkanEmbeddedWindow.CreateSurface(instance)
|
||||||
|
: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs
Normal file
33
Ryujinx.Ava/Ui/Controls/VulkanEmbeddedWindow.cs
Normal file
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,220 +0,0 @@
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Media;
|
|
||||||
using Avalonia.Platform;
|
|
||||||
using Avalonia.Rendering.SceneGraph;
|
|
||||||
using Avalonia.Skia;
|
|
||||||
using Avalonia.Threading;
|
|
||||||
using Ryujinx.Ava.Ui.Backend.Vulkan;
|
|
||||||
using Ryujinx.Ava.Ui.Vulkan;
|
|
||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using Ryujinx.Graphics.Vulkan;
|
|
||||||
using Silk.NET.Vulkan;
|
|
||||||
using SkiaSharp;
|
|
||||||
using SPB.Windowing;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Controls
|
|
||||||
{
|
|
||||||
internal class VulkanRendererControl : RendererControl
|
|
||||||
{
|
|
||||||
private const int MaxImagesInFlight = 3;
|
|
||||||
|
|
||||||
private VulkanPlatformInterface _platformInterface;
|
|
||||||
private ConcurrentQueue<PresentImageInfo> _imagesInFlight;
|
|
||||||
private PresentImageInfo _currentImage;
|
|
||||||
|
|
||||||
public VulkanRendererControl(GraphicsDebugLevel graphicsDebugLevel) : base(graphicsDebugLevel)
|
|
||||||
{
|
|
||||||
_platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
|
||||||
|
|
||||||
_imagesInFlight = new ConcurrentQueue<PresentImageInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void DestroyBackgroundContext()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ICustomDrawOperation CreateDrawOperation()
|
|
||||||
{
|
|
||||||
return new VulkanDrawOperation(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnDetachedFromVisualTree(e);
|
|
||||||
|
|
||||||
_imagesInFlight.Clear();
|
|
||||||
|
|
||||||
if (_platformInterface.MainSurface.Display != null)
|
|
||||||
{
|
|
||||||
_platformInterface.MainSurface.Display.Presented -= Window_Presented;
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentImage?.Put();
|
|
||||||
_currentImage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnAttachedToVisualTree(e);
|
|
||||||
|
|
||||||
_platformInterface.MainSurface.Display.Presented += Window_Presented;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Window_Presented(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
_platformInterface.MainSurface.Device.QueueWaitIdle();
|
|
||||||
_currentImage?.Put();
|
|
||||||
_currentImage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Render(DrawingContext context)
|
|
||||||
{
|
|
||||||
base.Render(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void CreateWindow()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void MakeCurrent()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void MakeCurrent(SwappableNativeWindowBase window)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void Present(object image)
|
|
||||||
{
|
|
||||||
Image = image;
|
|
||||||
|
|
||||||
_imagesInFlight.Enqueue((PresentImageInfo)image);
|
|
||||||
|
|
||||||
if (_imagesInFlight.Count > MaxImagesInFlight)
|
|
||||||
{
|
|
||||||
_imagesInFlight.TryDequeue(out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(InvalidateVisual);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PresentImageInfo GetImage()
|
|
||||||
{
|
|
||||||
lock (_imagesInFlight)
|
|
||||||
{
|
|
||||||
if (!_imagesInFlight.TryDequeue(out _currentImage))
|
|
||||||
{
|
|
||||||
_currentImage = (PresentImageInfo)Image;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _currentImage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class VulkanDrawOperation : ICustomDrawOperation
|
|
||||||
{
|
|
||||||
public Rect Bounds { get; }
|
|
||||||
|
|
||||||
private readonly VulkanRendererControl _control;
|
|
||||||
private bool _isDestroyed;
|
|
||||||
|
|
||||||
public VulkanDrawOperation(VulkanRendererControl control)
|
|
||||||
{
|
|
||||||
_control = control;
|
|
||||||
Bounds = _control.Bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_isDestroyed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isDestroyed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(ICustomDrawOperation other)
|
|
||||||
{
|
|
||||||
return other is VulkanDrawOperation operation && Equals(this, operation) && operation.Bounds == Bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HitTest(Point p)
|
|
||||||
{
|
|
||||||
return Bounds.Contains(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe void Render(IDrawingContextImpl context)
|
|
||||||
{
|
|
||||||
if (_isDestroyed || _control.Image == null || _control.RenderSize.Width == 0 || _control.RenderSize.Height == 0 ||
|
|
||||||
context is not ISkiaDrawingContextImpl skiaDrawingContextImpl)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var image = _control.GetImage();
|
|
||||||
|
|
||||||
if (!image.State.IsValid)
|
|
||||||
{
|
|
||||||
_control._currentImage = null;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var gpu = AvaloniaLocator.Current.GetService<VulkanSkiaGpu>();
|
|
||||||
|
|
||||||
image.Get();
|
|
||||||
|
|
||||||
var imageInfo = new GRVkImageInfo()
|
|
||||||
{
|
|
||||||
CurrentQueueFamily = _control._platformInterface.PhysicalDevice.QueueFamilyIndex,
|
|
||||||
Format = (uint)Format.R8G8B8A8Unorm,
|
|
||||||
Image = image.Image.Handle,
|
|
||||||
ImageLayout = (uint)ImageLayout.TransferSrcOptimal,
|
|
||||||
ImageTiling = (uint)ImageTiling.Optimal,
|
|
||||||
ImageUsageFlags = (uint)(ImageUsageFlags.ImageUsageColorAttachmentBit
|
|
||||||
| ImageUsageFlags.ImageUsageTransferSrcBit
|
|
||||||
| ImageUsageFlags.ImageUsageTransferDstBit),
|
|
||||||
LevelCount = 1,
|
|
||||||
SampleCount = 1,
|
|
||||||
Protected = false,
|
|
||||||
Alloc = new GRVkAlloc()
|
|
||||||
{
|
|
||||||
Memory = image.Memory.Handle,
|
|
||||||
Flags = 0,
|
|
||||||
Offset = image.MemoryOffset,
|
|
||||||
Size = image.MemorySize
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using var backendTexture = new GRBackendRenderTarget(
|
|
||||||
(int)image.Extent.Width,
|
|
||||||
(int)image.Extent.Height,
|
|
||||||
1,
|
|
||||||
imageInfo);
|
|
||||||
|
|
||||||
var vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
|
||||||
|
|
||||||
using var surface = SKSurface.Create(
|
|
||||||
skiaDrawingContextImpl.GrContext,
|
|
||||||
backendTexture,
|
|
||||||
GRSurfaceOrigin.TopLeft,
|
|
||||||
SKColorType.Rgba8888);
|
|
||||||
|
|
||||||
if (surface == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rect = new Rect(new Point(), new Size(image.Extent.Width, image.Extent.Height));
|
|
||||||
|
|
||||||
using var snapshot = surface.Snapshot();
|
|
||||||
skiaDrawingContextImpl.SkCanvas.DrawImage(snapshot, rect.ToSKRect(), _control.Bounds.ToSKRect(),
|
|
||||||
new SKPaint());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
113
Ryujinx.Ava/Ui/Controls/Win32NativeInterop.cs
Normal file
113
Ryujinx.Ava/Ui/Controls/Win32NativeInterop.cs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
{
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
internal class Win32NativeInterop
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum ClassStyles : uint
|
||||||
|
{
|
||||||
|
CS_CLASSDC = 0x40,
|
||||||
|
CS_OWNDC = 0x20,
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum WindowStyles : uint
|
||||||
|
{
|
||||||
|
WS_CHILD = 0x40000000
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Cursors : uint
|
||||||
|
{
|
||||||
|
IDC_ARROW = 32512
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum WindowsMessages : uint
|
||||||
|
{
|
||||||
|
MOUSEMOVE = 0x0200,
|
||||||
|
LBUTTONDOWN = 0x0201,
|
||||||
|
LBUTTONUP = 0x0202,
|
||||||
|
LBUTTONDBLCLK = 0x0203,
|
||||||
|
RBUTTONDOWN = 0x0204,
|
||||||
|
RBUTTONUP = 0x0205,
|
||||||
|
RBUTTONDBLCLK = 0x0206,
|
||||||
|
MBUTTONDOWN = 0x0207,
|
||||||
|
MBUTTONUP = 0x0208,
|
||||||
|
MBUTTONDBLCLK = 0x0209,
|
||||||
|
MOUSEWHEEL = 0x020A,
|
||||||
|
XBUTTONDOWN = 0x020B,
|
||||||
|
XBUTTONUP = 0x020C,
|
||||||
|
XBUTTONDBLCLK = 0x020D,
|
||||||
|
MOUSEHWHEEL = 0x020E,
|
||||||
|
MOUSELAST = 0x020E
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
|
||||||
|
internal delegate IntPtr WindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||||
|
public struct WNDCLASSEX
|
||||||
|
{
|
||||||
|
public int cbSize;
|
||||||
|
public ClassStyles style;
|
||||||
|
[MarshalAs(UnmanagedType.FunctionPtr)]
|
||||||
|
public WindowProc lpfnWndProc; // not WndProc
|
||||||
|
public int cbClsExtra;
|
||||||
|
public int cbWndExtra;
|
||||||
|
public IntPtr hInstance;
|
||||||
|
public IntPtr hIcon;
|
||||||
|
public IntPtr hCursor;
|
||||||
|
public IntPtr hbrBackground;
|
||||||
|
[MarshalAs(UnmanagedType.LPWStr)]
|
||||||
|
public string lpszMenuName;
|
||||||
|
[MarshalAs(UnmanagedType.LPWStr)]
|
||||||
|
public string lpszClassName;
|
||||||
|
public IntPtr hIconSm;
|
||||||
|
|
||||||
|
public static WNDCLASSEX Create()
|
||||||
|
{
|
||||||
|
return new WNDCLASSEX
|
||||||
|
{
|
||||||
|
cbSize = Marshal.SizeOf<WNDCLASSEX>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
public static extern ushort RegisterClassEx(ref WNDCLASSEX param);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
public static extern short UnregisterClass([MarshalAs(UnmanagedType.LPWStr)] string lpClassName, IntPtr instance);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||||
|
public static extern IntPtr DefWindowProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
public static extern IntPtr GetModuleHandle(string lpModuleName);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public static extern bool DestroyWindow(IntPtr hwnd);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
public static extern IntPtr CreateWindowEx(
|
||||||
|
uint dwExStyle,
|
||||||
|
string lpClassName,
|
||||||
|
string lpWindowName,
|
||||||
|
WindowStyles dwStyle,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int nWidth,
|
||||||
|
int nHeight,
|
||||||
|
IntPtr hWndParent,
|
||||||
|
IntPtr hMenu,
|
||||||
|
IntPtr hInstance,
|
||||||
|
IntPtr lpParam);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ using Ryujinx.Audio.Backends.SoundIo;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Input;
|
using Ryujinx.Ava.Input;
|
||||||
using Ryujinx.Ava.Ui.Controls;
|
using Ryujinx.Ava.Ui.Controls;
|
||||||
using Ryujinx.Ava.Ui.Vulkan;
|
|
||||||
using Ryujinx.Ava.Ui.Windows;
|
using Ryujinx.Ava.Ui.Windows;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
@ -252,34 +251,19 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
{
|
{
|
||||||
_gpuIds = new List<string>();
|
_gpuIds = new List<string>();
|
||||||
List<string> names = new List<string>();
|
List<string> names = new List<string>();
|
||||||
if (!Program.UseVulkan)
|
var devices = VulkanRenderer.GetPhysicalDevices();
|
||||||
{
|
|
||||||
var devices = VulkanRenderer.GetPhysicalDevices();
|
|
||||||
|
|
||||||
if (devices.Length == 0)
|
if (devices.Length == 0)
|
||||||
{
|
{
|
||||||
IsVulkanAvailable = false;
|
IsVulkanAvailable = false;
|
||||||
GraphicsBackendIndex = 1;
|
GraphicsBackendIndex = 1;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var device in devices)
|
|
||||||
{
|
|
||||||
_gpuIds.Add(device.Id);
|
|
||||||
names.Add($"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var device in VulkanPhysicalDevice.SuitableDevices)
|
foreach (var device in devices)
|
||||||
{
|
{
|
||||||
_gpuIds.Add(
|
_gpuIds.Add(device.Id);
|
||||||
VulkanInitialization.StringFromIdPair(device.Value.VendorID, device.Value.DeviceID));
|
names.Add($"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}");
|
||||||
var value = device.Value;
|
|
||||||
var name = value.DeviceName;
|
|
||||||
names.Add(
|
|
||||||
$"{Marshal.PtrToStringAnsi((IntPtr)name)} {(device.Value.DeviceType == PhysicalDeviceType.DiscreteGpu ? "(dGPU)" : "")}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,7 +391,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
_previousVolumeLevel = Volume;
|
_previousVolumeLevel = Volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveSettings()
|
public void SaveSettings()
|
||||||
{
|
{
|
||||||
ConfigurationState config = ConfigurationState.Instance;
|
ConfigurationState config = ConfigurationState.Instance;
|
||||||
|
|
||||||
|
@ -422,8 +406,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
config.System.TimeZone.Value = TimeZone;
|
config.System.TimeZone.Value = TimeZone;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool requiresRestart = config.Graphics.GraphicsBackend.Value != (GraphicsBackend)GraphicsBackendIndex;
|
|
||||||
|
|
||||||
config.Logger.EnableError.Value = EnableError;
|
config.Logger.EnableError.Value = EnableError;
|
||||||
config.Logger.EnableTrace.Value = EnableTrace;
|
config.Logger.EnableTrace.Value = EnableTrace;
|
||||||
config.Logger.EnableWarn.Value = EnableWarn;
|
config.Logger.EnableWarn.Value = EnableWarn;
|
||||||
|
@ -456,19 +438,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
config.System.Language.Value = (Language)Language;
|
config.System.Language.Value = (Language)Language;
|
||||||
config.System.Region.Value = (Region)Region;
|
config.System.Region.Value = (Region)Region;
|
||||||
|
|
||||||
var selectedGpu = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
|
||||||
if (!requiresRestart)
|
|
||||||
{
|
|
||||||
var platform = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
|
||||||
if (platform != null)
|
|
||||||
{
|
|
||||||
var physicalDevice = platform.PhysicalDevice;
|
|
||||||
|
|
||||||
requiresRestart = physicalDevice.DeviceId != selectedGpu;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Graphics.PreferredGpu.Value = selectedGpu;
|
|
||||||
|
|
||||||
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
||||||
{
|
{
|
||||||
|
@ -507,20 +477,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
MainWindow.UpdateGraphicsConfig();
|
MainWindow.UpdateGraphicsConfig();
|
||||||
|
|
||||||
_previousVolumeLevel = Volume;
|
_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()
|
public void RevertIfNotSaved()
|
||||||
|
|
27
Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml
Normal file
27
Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<window:StyleableWindow xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
|
x:Class="Ryujinx.Ava.Ui.Windows.ContentDialogOverlayWindow"
|
||||||
|
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||||
|
Title="ContentDialogOverlayWindow">
|
||||||
|
<window:StyleableWindow.Styles>
|
||||||
|
<Style Selector="ui|ContentDialog /template/ Panel#LayoutRoot">
|
||||||
|
<Setter Property="Background"
|
||||||
|
Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
</window:StyleableWindow.Styles>
|
||||||
|
<ContentControl
|
||||||
|
Focusable="False"
|
||||||
|
IsVisible="False"
|
||||||
|
KeyboardNavigation.IsTabStop="False">
|
||||||
|
<ui:ContentDialog Name="ContentDialog"
|
||||||
|
IsPrimaryButtonEnabled="True"
|
||||||
|
IsSecondaryButtonEnabled="True"
|
||||||
|
IsVisible="False" />
|
||||||
|
</ContentControl>
|
||||||
|
</window:StyleableWindow>
|
25
Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml.cs
Normal file
25
Ryujinx.Ava/Ui/Windows/ContentDialogOverlayWindow.axaml.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -462,6 +462,7 @@
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||||
IsVisible="{Binding ShowLoadProgress}"
|
IsVisible="{Binding ShowLoadProgress}"
|
||||||
|
Name="LoadingView"
|
||||||
ZIndex="1000">
|
ZIndex="1000">
|
||||||
<Grid
|
<Grid
|
||||||
Margin="40"
|
Margin="40"
|
||||||
|
|
|
@ -58,7 +58,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
internal AppHost AppHost { get; private set; }
|
internal AppHost AppHost { get; private set; }
|
||||||
public InputManager InputManager { get; private set; }
|
public InputManager InputManager { get; private set; }
|
||||||
|
|
||||||
internal RendererControl RendererControl { get; private set; }
|
internal RendererHost RendererControl { get; private set; }
|
||||||
internal MainWindowViewModel ViewModel { get; private set; }
|
internal MainWindowViewModel ViewModel { get; private set; }
|
||||||
public SettingsWindow SettingsWindow { get; set; }
|
public SettingsWindow SettingsWindow { get; set; }
|
||||||
|
|
||||||
|
@ -237,7 +237,16 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
|
|
||||||
_mainViewContent = MainContent.Content as Control;
|
_mainViewContent = MainContent.Content as Control;
|
||||||
|
|
||||||
RendererControl = Program.UseVulkan ? new VulkanRendererControl(ConfigurationState.Instance.Logger.GraphicsDebugLevel) : new OpenGLRendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
RendererControl = new RendererHost(ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
||||||
|
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
|
||||||
|
{
|
||||||
|
RendererControl.CreateOpenGL();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RendererControl.CreateVulkan();
|
||||||
|
}
|
||||||
|
|
||||||
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
|
AppHost = new AppHost(RendererControl, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
|
||||||
|
|
||||||
if (!AppHost.LoadGuestApplication().Result)
|
if (!AppHost.LoadGuestApplication().Result)
|
||||||
|
@ -296,8 +305,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
|
|
||||||
public void SwitchToGameControl(bool startFullscreen = false)
|
public void SwitchToGameControl(bool startFullscreen = false)
|
||||||
{
|
{
|
||||||
ViewModel.ShowContent = true;
|
|
||||||
ViewModel.ShowLoadProgress = false;
|
ViewModel.ShowLoadProgress = false;
|
||||||
|
ViewModel.ShowContent = true;
|
||||||
ViewModel.IsLoadingIndeterminate = false;
|
ViewModel.IsLoadingIndeterminate = false;
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
@ -346,17 +355,17 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
if (MainContent.Content != _mainViewContent)
|
|
||||||
{
|
|
||||||
MainContent.Content = _mainViewContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewModel.ShowMenuAndStatusBar = true;
|
ViewModel.ShowMenuAndStatusBar = true;
|
||||||
ViewModel.ShowContent = true;
|
ViewModel.ShowContent = true;
|
||||||
ViewModel.ShowLoadProgress = false;
|
ViewModel.ShowLoadProgress = false;
|
||||||
ViewModel.IsLoadingIndeterminate = false;
|
ViewModel.IsLoadingIndeterminate = false;
|
||||||
Cursor = Cursor.Default;
|
Cursor = Cursor.Default;
|
||||||
|
|
||||||
|
if (MainContent.Content != _mainViewContent)
|
||||||
|
{
|
||||||
|
MainContent.Content = _mainViewContent;
|
||||||
|
}
|
||||||
|
|
||||||
AppHost = null;
|
AppHost = null;
|
||||||
|
|
||||||
HandleRelaunch();
|
HandleRelaunch();
|
||||||
|
|
|
@ -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();
|
Close();
|
||||||
}
|
}
|
||||||
|
@ -224,14 +224,14 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
Close();
|
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();
|
ControllerSettings?.SaveCurrentProfile();
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
{
|
{
|
||||||
public interface IWindow
|
public interface IWindow
|
||||||
{
|
{
|
||||||
void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback);
|
void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
|
||||||
|
|
||||||
void SetSize(int width, int height);
|
void SetSize(int width, int height);
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Window
|
||||||
public CommandType CommandType => CommandType.WindowPresent;
|
public CommandType CommandType => CommandType.WindowPresent;
|
||||||
private TableRef<ThreadedTexture> _texture;
|
private TableRef<ThreadedTexture> _texture;
|
||||||
private ImageCrop _crop;
|
private ImageCrop _crop;
|
||||||
private TableRef<Action<object>> _swapBuffersCallback;
|
private TableRef<Action> _swapBuffersCallback;
|
||||||
|
|
||||||
public void Set(TableRef<ThreadedTexture> texture, ImageCrop crop, TableRef<Action<object>> swapBuffersCallback)
|
public void Set(TableRef<ThreadedTexture> texture, ImageCrop crop, TableRef<Action> swapBuffersCallback)
|
||||||
{
|
{
|
||||||
_texture = texture;
|
_texture = texture;
|
||||||
_crop = crop;
|
_crop = crop;
|
||||||
|
|
|
@ -16,13 +16,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
_impl = impl;
|
_impl = impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Present(ITexture texture, ImageCrop crop, Action<object> 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.
|
// 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.
|
// This is a multithread rate limit - we can't be more than one frame behind the command queue.
|
||||||
|
|
||||||
_renderer.WaitForFrame();
|
_renderer.WaitForFrame();
|
||||||
_renderer.New<WindowPresentCommand>().Set(new TableRef<ThreadedTexture>(_renderer, texture as ThreadedTexture), crop, new TableRef<Action<object>>(_renderer, swapBuffersCallback));
|
_renderer.New<WindowPresentCommand>().Set(new TableRef<ThreadedTexture>(_renderer, texture as ThreadedTexture), crop, new TableRef<Action>(_renderer, swapBuffersCallback));
|
||||||
_renderer.QueueCommand();
|
_renderer.QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -191,7 +191,7 @@ namespace Ryujinx.Graphics.Gpu
|
||||||
/// If the queue is empty, then no texture is presented.
|
/// If the queue is empty, then no texture is presented.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="swapBuffersCallback">Callback method to call when a new texture should be presented on the screen</param>
|
/// <param name="swapBuffersCallback">Callback method to call when a new texture should be presented on the screen</param>
|
||||||
public void Present(Action<object> swapBuffersCallback)
|
public void Present(Action swapBuffersCallback)
|
||||||
{
|
{
|
||||||
_context.AdvanceSequence();
|
_context.AdvanceSequence();
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
private int _width;
|
private int _width;
|
||||||
private int _height;
|
private int _height;
|
||||||
private bool _sizeChanged;
|
|
||||||
private int _copyFramebufferHandle;
|
private int _copyFramebufferHandle;
|
||||||
private int _stagingFrameBuffer;
|
|
||||||
private int[] _stagingTextures;
|
|
||||||
private int _currentTexture;
|
|
||||||
|
|
||||||
internal BackgroundContextWorker BackgroundContext { get; private set; }
|
internal BackgroundContextWorker BackgroundContext { get; private set; }
|
||||||
|
|
||||||
|
@ -25,28 +21,15 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
public Window(OpenGLRenderer renderer)
|
public Window(OpenGLRenderer renderer)
|
||||||
{
|
{
|
||||||
_renderer = renderer;
|
_renderer = renderer;
|
||||||
_stagingTextures = new int[TextureCount];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback)
|
public void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
|
||||||
{
|
{
|
||||||
GL.Disable(EnableCap.FramebufferSrgb);
|
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();
|
(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.ReadFramebuffer, oldReadFramebufferHandle);
|
||||||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
|
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
|
||||||
|
@ -59,41 +42,17 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
public void ChangeVSyncMode(bool vsyncEnabled) { }
|
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)
|
public void SetSize(int width, int height)
|
||||||
{
|
{
|
||||||
_width = width;
|
_width = width;
|
||||||
_height = height;
|
_height = height;
|
||||||
_sizeChanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop, Action<object> swapBuffersCallback)
|
private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop, Action swapBuffersCallback)
|
||||||
{
|
{
|
||||||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
|
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
|
||||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);
|
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;
|
TextureView viewConverted = view.Format.IsBgr() ? _renderer.TextureCopy.BgraSwap(view) : view;
|
||||||
|
|
||||||
GL.FramebufferTexture(
|
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.
|
// Set clip control, viewport and the framebuffer to the output to placate overlays and OBS capture.
|
||||||
GL.ClipControl(ClipOrigin.LowerLeft, ClipDepthMode.NegativeOneToOne);
|
GL.ClipControl(ClipOrigin.LowerLeft, ClipDepthMode.NegativeOneToOne);
|
||||||
GL.Viewport(0, 0, _width, _height);
|
GL.Viewport(0, 0, _width, _height);
|
||||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, drawFramebuffer);
|
|
||||||
|
|
||||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, _stagingFrameBuffer);
|
swapBuffersCallback();
|
||||||
|
|
||||||
swapBuffersCallback((object)_stagingTextures[_currentTexture]);
|
|
||||||
_currentTexture = ++_currentTexture % _stagingTextures.Length;
|
|
||||||
|
|
||||||
((Pipeline)_renderer.Pipeline).RestoreClipControl();
|
((Pipeline)_renderer.Pipeline).RestoreClipControl();
|
||||||
((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
|
((Pipeline)_renderer.Pipeline).RestoreScissor0Enable();
|
||||||
|
@ -246,14 +201,6 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
_copyFramebufferHandle = 0;
|
_copyFramebufferHandle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_stagingFrameBuffer != 0)
|
|
||||||
{
|
|
||||||
GL.DeleteTextures(_stagingTextures.Length, _stagingTextures);
|
|
||||||
GL.DeleteFramebuffer(_stagingFrameBuffer);
|
|
||||||
_stagingFrameBuffer = 0;
|
|
||||||
_stagingTextures = 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<DisposableImage>[] _images;
|
|
||||||
private Auto<DisposableImageView>[] _imageViews;
|
|
||||||
private Auto<MemoryAllocation>[] _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<DisposableImage>[ImageCount];
|
|
||||||
_imageAllocationAuto = new Auto<MemoryAllocation>[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<DisposableImageView>[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<DisposableImage>(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<MemoryAllocation>(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<DisposableImageView> 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<DisposableImageView>(new DisposableImageView(_gd.Api, _device, imageView));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override unsafe void Present(ITexture texture, ImageCrop crop, Action<object> 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<bool> StateChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PresentImageInfo
|
|
||||||
{
|
|
||||||
private readonly Auto<DisposableImage> _image;
|
|
||||||
private readonly Auto<MemoryAllocation> _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<DisposableImage> image,
|
|
||||||
Auto<MemoryAllocation> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,7 +20,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private SurfaceKHR _surface;
|
private SurfaceKHR _surface;
|
||||||
private PhysicalDevice _physicalDevice;
|
private PhysicalDevice _physicalDevice;
|
||||||
private Device _device;
|
private Device _device;
|
||||||
private uint _queueFamilyIndex;
|
|
||||||
private WindowBase _window;
|
private WindowBase _window;
|
||||||
|
|
||||||
internal FormatCapabilities FormatCapabilities { get; private set; }
|
internal FormatCapabilities FormatCapabilities { get; private set; }
|
||||||
|
@ -37,7 +36,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
internal ExtDebugReport DebugReportApi { get; private set; }
|
internal ExtDebugReport DebugReportApi { get; private set; }
|
||||||
|
|
||||||
internal uint QueueFamilyIndex { get; private set; }
|
internal uint QueueFamilyIndex { get; private set; }
|
||||||
public bool IsOffScreen { get; }
|
|
||||||
internal Queue Queue { get; private set; }
|
internal Queue Queue { get; private set; }
|
||||||
internal Queue BackgroundQueue { get; private set; }
|
internal Queue BackgroundQueue { get; private set; }
|
||||||
internal object BackgroundQueueLock { get; private set; }
|
internal object BackgroundQueueLock { get; private set; }
|
||||||
|
@ -94,22 +92,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
Samplers = new HashSet<SamplerHolder>();
|
Samplers = new HashSet<SamplerHolder>();
|
||||||
}
|
}
|
||||||
|
|
||||||
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<ShaderCollection>();
|
|
||||||
Textures = new HashSet<ITexture>();
|
|
||||||
Samplers = new HashSet<SamplerHolder>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void LoadFeatures(string[] supportedExtensions, uint maxQueueCount, uint queueFamilyIndex)
|
private unsafe void LoadFeatures(string[] supportedExtensions, uint maxQueueCount, uint queueFamilyIndex)
|
||||||
{
|
{
|
||||||
FormatCapabilities = new FormatCapabilities(Api, _physicalDevice);
|
FormatCapabilities = new FormatCapabilities(Api, _physicalDevice);
|
||||||
|
@ -286,34 +268,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_window = new Window(this, _surface, _physicalDevice, _device);
|
_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)
|
public BufferHandle CreateBuffer(int size)
|
||||||
{
|
{
|
||||||
return BufferManager.CreateWithHandle(this, size, false);
|
return BufferManager.CreateWithHandle(this, size, false);
|
||||||
|
@ -519,14 +473,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public void Initialize(GraphicsDebugLevel logLevel)
|
public void Initialize(GraphicsDebugLevel logLevel)
|
||||||
{
|
{
|
||||||
if (IsOffScreen)
|
SetupContext(logLevel);
|
||||||
{
|
|
||||||
SetupOffScreenContext(logLevel);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SetupContext(logLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintGpuInformation();
|
PrintGpuInformation();
|
||||||
}
|
}
|
||||||
|
@ -638,15 +585,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
sampler.Dispose();
|
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
|
// Last step destroy the instance
|
||||||
Api.DestroyInstance(_instance, null);
|
Api.DestroyInstance(_instance, null);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,7 +217,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe override void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback)
|
public unsafe override void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
|
||||||
{
|
{
|
||||||
uint nextImage = 0;
|
uint nextImage = 0;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
public bool ScreenCaptureRequested { get; set; }
|
public bool ScreenCaptureRequested { get; set; }
|
||||||
|
|
||||||
public abstract void Dispose();
|
public abstract void Dispose();
|
||||||
public abstract void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback);
|
public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
|
||||||
public abstract void SetSize(int width, int height);
|
public abstract void SetSize(int width, int height);
|
||||||
public abstract void ChangeVSyncMode(bool vsyncEnabled);
|
public abstract void ChangeVSyncMode(bool vsyncEnabled);
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@ namespace Ryujinx.HLE
|
||||||
return Gpu.Window.ConsumeFrameAvailable();
|
return Gpu.Window.ConsumeFrameAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PresentFrame(Action<object> swapBuffersCallback)
|
public void PresentFrame(Action swapBuffersCallback)
|
||||||
{
|
{
|
||||||
Gpu.Window.Present(swapBuffersCallback);
|
Gpu.Window.Present(swapBuffersCallback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ namespace Ryujinx.Headless.SDL2.OpenGL
|
||||||
|
|
||||||
GL.ClearColor(0, 0, 0, 1.0f);
|
GL.ClearColor(0, 0, 0, 1.0f);
|
||||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||||
SwapBuffers(0);
|
SwapBuffers();
|
||||||
|
|
||||||
Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
|
Renderer?.Window.SetSize(DefaultWidth, DefaultHeight);
|
||||||
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
|
MouseDriver.SetClientSize(DefaultWidth, DefaultHeight);
|
||||||
|
@ -156,28 +156,8 @@ namespace Ryujinx.Headless.SDL2.OpenGL
|
||||||
_openGLContext.Dispose();
|
_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);
|
SDL_GL_SwapWindow(WindowHandle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,6 @@ namespace Ryujinx.Headless.SDL2.Vulkan
|
||||||
Device.DisposeGpu();
|
Device.DisposeGpu();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SwapBuffers(object texture) { }
|
protected override void SwapBuffers() { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,7 +157,7 @@ namespace Ryujinx.Headless.SDL2
|
||||||
|
|
||||||
protected abstract void FinalizeWindowRenderer();
|
protected abstract void FinalizeWindowRenderer();
|
||||||
|
|
||||||
protected abstract void SwapBuffers(object image);
|
protected abstract void SwapBuffers();
|
||||||
|
|
||||||
public abstract SDL_WindowFlags GetWindowFlags();
|
public abstract SDL_WindowFlags GetWindowFlags();
|
||||||
|
|
||||||
|
@ -202,7 +202,7 @@ namespace Ryujinx.Headless.SDL2
|
||||||
|
|
||||||
while (Device.ConsumeFrameAvailable())
|
while (Device.ConsumeFrameAvailable())
|
||||||
{
|
{
|
||||||
Device.PresentFrame((texture) => { SwapBuffers(texture); });
|
Device.PresentFrame(SwapBuffers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_ticks >= _ticksPerFrame)
|
if (_ticks >= _ticksPerFrame)
|
||||||
|
|
|
@ -97,31 +97,11 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
GL.ClearColor(0, 0, 0, 1.0f);
|
GL.ClearColor(0, 0, 0, 1.0f);
|
||||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
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();
|
_nativeWindow.SwapBuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
public abstract void InitializeRenderer();
|
public abstract void InitializeRenderer();
|
||||||
|
|
||||||
public abstract void SwapBuffers(object image);
|
public abstract void SwapBuffers();
|
||||||
|
|
||||||
protected abstract string GetGpuBackendName();
|
protected abstract string GetGpuBackendName();
|
||||||
|
|
||||||
|
@ -426,7 +426,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
while (Device.ConsumeFrameAvailable())
|
while (Device.ConsumeFrameAvailable())
|
||||||
{
|
{
|
||||||
Device.PresentFrame((texture) => { SwapBuffers(texture);});
|
Device.PresentFrame(SwapBuffers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_ticks >= _ticksPerFrame)
|
if (_ticks >= _ticksPerFrame)
|
||||||
|
|
|
@ -63,7 +63,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
public override void InitializeRenderer() { }
|
public override void InitializeRenderer() { }
|
||||||
|
|
||||||
public override void SwapBuffers(object image) { }
|
public override void SwapBuffers() { }
|
||||||
|
|
||||||
protected override string GetGpuBackendName()
|
protected override string GetGpuBackendName()
|
||||||
{
|
{
|
||||||
|
|
Reference in a new issue