mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-02-21 16:43:35 +00:00
Add support for avalonia (#6)
* add avalonia support * only lock around skia flush * addressed review * cleanup * add fallback size if avalonia attempts to render but the window size is 0. read desktop scale after enabling dpi check * fix getting window handle on linux. skip render is size is 0
This commit is contained in:
parent
2000070330
commit
8a1bdf1f1e
38 changed files with 3095 additions and 310 deletions
|
@ -1,5 +1,6 @@
|
||||||
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;
|
||||||
|
@ -13,6 +14,7 @@ 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.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;
|
||||||
|
@ -22,6 +24,7 @@ using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.GAL.Multithreading;
|
using Ryujinx.Graphics.GAL.Multithreading;
|
||||||
using Ryujinx.Graphics.Gpu;
|
using Ryujinx.Graphics.Gpu;
|
||||||
using Ryujinx.Graphics.OpenGL;
|
using Ryujinx.Graphics.OpenGL;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
|
@ -590,7 +593,23 @@ namespace Ryujinx.Ava
|
||||||
{
|
{
|
||||||
VirtualFileSystem.ReloadKeySet();
|
VirtualFileSystem.ReloadKeySet();
|
||||||
|
|
||||||
IRenderer renderer = new Renderer();
|
IRenderer renderer;
|
||||||
|
|
||||||
|
if (Program.UseVulkan)
|
||||||
|
{
|
||||||
|
var vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
|
renderer = new VulkanGraphicsDevice(vulkan.Instance.InternalHandle,
|
||||||
|
vulkan.Device.InternalHandle,
|
||||||
|
vulkan.PhysicalDevice.InternalHandle,
|
||||||
|
vulkan.Device.Queue.InternalHandle,
|
||||||
|
vulkan.PhysicalDevice.QueueFamilyIndex,
|
||||||
|
vulkan.Device.Lock);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderer = new Renderer();
|
||||||
|
}
|
||||||
|
|
||||||
IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver();
|
IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver();
|
||||||
|
|
||||||
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
|
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
|
||||||
|
@ -800,9 +819,12 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
||||||
|
|
||||||
(_renderer as Renderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Renderer.GameContext));
|
if (!Program.UseVulkan)
|
||||||
|
{
|
||||||
|
(_renderer as Renderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext((Renderer as OpenGLRendererControl).GameContext));
|
||||||
|
|
||||||
Renderer.MakeCurrent();
|
Renderer.MakeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||||
|
|
||||||
|
@ -861,8 +883,6 @@ namespace Ryujinx.Ava
|
||||||
dockedMode += $" ({scale}x)";
|
dockedMode += $" ({scale}x)";
|
||||||
}
|
}
|
||||||
|
|
||||||
string vendor = _renderer is Renderer renderer ? renderer.GpuVendor : "";
|
|
||||||
|
|
||||||
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
||||||
Device.EnableDeviceVsync,
|
Device.EnableDeviceVsync,
|
||||||
Device.GetVolume(),
|
Device.GetVolume(),
|
||||||
|
@ -870,7 +890,7 @@ namespace Ryujinx.Ava
|
||||||
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: {vendor}"));
|
$"GPU: {_renderer.GetHardwareInfo().GpuVendor}"));
|
||||||
|
|
||||||
Renderer.Present(image);
|
Renderer.Present(image);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using Avalonia;
|
||||||
using Avalonia.OpenGL;
|
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;
|
||||||
|
@ -11,9 +12,12 @@ 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 Silk.NET.Vulkan.Extensions.EXT;
|
||||||
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -25,17 +29,20 @@ namespace Ryujinx.Ava
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
public static double WindowScaleFactor { get; set; }
|
public static double WindowScaleFactor { get; set; }
|
||||||
|
public static double ActualScaleFactor { get; set; }
|
||||||
public static string Version { get; private set; }
|
public static string Version { get; private set; }
|
||||||
public static string ConfigurationPath { get; private set; }
|
public static string ConfigurationPath { get; private set; }
|
||||||
public static string CommandLineProfile { get; set; }
|
public static string CommandLineProfile { get; set; }
|
||||||
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);
|
||||||
|
|
||||||
private const uint MB_ICONWARNING = 0x30;
|
private const uint MB_ICONWARNING = 0x30;
|
||||||
|
private const int BaseDpi = 96;
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
@ -66,7 +73,7 @@ namespace Ryujinx.Ava
|
||||||
EnableMultiTouch = true,
|
EnableMultiTouch = true,
|
||||||
EnableIme = true,
|
EnableIme = true,
|
||||||
UseEGL = false,
|
UseEGL = false,
|
||||||
UseGpu = true,
|
UseGpu = !UseVulkan,
|
||||||
GlProfiles = new List<GlVersion>()
|
GlProfiles = new List<GlVersion>()
|
||||||
{
|
{
|
||||||
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
||||||
|
@ -75,7 +82,7 @@ namespace Ryujinx.Ava
|
||||||
.With(new Win32PlatformOptions
|
.With(new Win32PlatformOptions
|
||||||
{
|
{
|
||||||
EnableMultitouch = true,
|
EnableMultitouch = true,
|
||||||
UseWgl = true,
|
UseWgl = !UseVulkan,
|
||||||
WglProfiles = new List<GlVersion>()
|
WglProfiles = new List<GlVersion>()
|
||||||
{
|
{
|
||||||
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
new GlVersion(GlProfileType.OpenGL, 4, 3)
|
||||||
|
@ -84,6 +91,18 @@ namespace Ryujinx.Ava
|
||||||
CompositionBackdropCornerRadius = 8f,
|
CompositionBackdropCornerRadius = 8f,
|
||||||
})
|
})
|
||||||
.UseSkia()
|
.UseSkia()
|
||||||
|
.With(new Ui.Vulkan.VulkanOptions()
|
||||||
|
{
|
||||||
|
ApplicationName = "Ryujinx.Graphics.Vulkan",
|
||||||
|
VulkanVersion = new Version(1, 2),
|
||||||
|
MaxQueueCount = 2,
|
||||||
|
PreferDiscreteGpu = true,
|
||||||
|
UseDebug = !PreviewerDetached ? false : ConfigurationState.Instance.Logger.GraphicsDebugLevel.Value != GraphicsDebugLevel.None,
|
||||||
|
})
|
||||||
|
.With(new SkiaOptions()
|
||||||
|
{
|
||||||
|
CustomGpuFactory = UseVulkan ? SkiaGpuFactory.CreateVulkanGpu : null
|
||||||
|
})
|
||||||
.AfterSetup(_ =>
|
.AfterSetup(_ =>
|
||||||
{
|
{
|
||||||
AvaloniaLocator.CurrentMutable
|
AvaloniaLocator.CurrentMutable
|
||||||
|
@ -136,9 +155,6 @@ namespace Ryujinx.Ava
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make process DPI aware for proper window sizing on high-res screens.
|
|
||||||
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
|
|
||||||
|
|
||||||
// Delete backup files after updating.
|
// Delete backup files after updating.
|
||||||
Task.Run(Updater.CleanupUpdate);
|
Task.Run(Updater.CleanupUpdate);
|
||||||
|
|
||||||
|
@ -162,6 +178,18 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
ReloadConfig();
|
ReloadConfig();
|
||||||
|
|
||||||
|
UseVulkan = PreviewerDetached ? ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan : false;
|
||||||
|
|
||||||
|
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();
|
||||||
|
ActualScaleFactor = ForceDpiAware.GetActualScaleFactor() / BaseDpi;
|
||||||
|
|
||||||
// Logging system information.
|
// Logging system information.
|
||||||
PrintSystemInfo();
|
PrintSystemInfo();
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,13 @@
|
||||||
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
||||||
<PackageReference Include="DynamicData" Version="7.7.14" />
|
<PackageReference Include="DynamicData" Version="7.7.14" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" Version="1.3.4" />
|
<PackageReference Include="FluentAvaloniaUI" Version="1.3.4" />
|
||||||
|
<PackageReference Include="OpenTK.Core" Version="4.7.2" />
|
||||||
|
|
||||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build9" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build9" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
|
||||||
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
|
<PackageReference Include="Silk.NET.Vulkan" Version="2.10.1" />
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.10.1" />
|
||||||
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.10.1" />
|
||||||
<PackageReference Include="SPB" Version="0.0.4-build17" />
|
<PackageReference Include="SPB" Version="0.0.4-build17" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||||
|
@ -39,6 +42,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
|
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
|
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
|
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
|
||||||
|
|
|
@ -135,7 +135,7 @@ namespace Ryujinx.Ava.Ui.Applet
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
_hiddenTextBox.Clear();
|
_hiddenTextBox.Clear();
|
||||||
_parent.GlRenderer.Focus();
|
_parent.RendererControl.Focus();
|
||||||
|
|
||||||
_parent = null;
|
_parent = null;
|
||||||
});
|
});
|
||||||
|
|
71
Ryujinx.Ava/Ui/Backend/BackendSurface.cs
Normal file
71
Ryujinx.Ava/Ui/Backend/BackendSurface.cs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
IsDisposed = true;
|
||||||
|
|
||||||
|
if (_display != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
XCloseDisplay(_display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
Ryujinx.Ava/Ui/Backend/Interop.cs
Normal file
49
Ryujinx.Ava/Ui/Backend/Interop.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
23
Ryujinx.Ava/Ui/Backend/SkiaGpuFactory.cs
Normal file
23
Ryujinx.Ava/Ui/Backend/SkiaGpuFactory.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs
Normal file
13
Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
{
|
||||||
|
public static class ResultExtensions
|
||||||
|
{
|
||||||
|
public static void ThrowOnError(this Result result)
|
||||||
|
{
|
||||||
|
if (result != Result.Success) throw new Exception($"Unexpected API error \"{result}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs
Normal file
134
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
using System;
|
||||||
|
using Avalonia.Skia;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Backend.Vulkan
|
||||||
|
{
|
||||||
|
internal class VulkanRenderTarget : ISkiaGpuRenderTarget
|
||||||
|
{
|
||||||
|
public GRContext GrContext { get; set; }
|
||||||
|
|
||||||
|
private readonly VulkanSurfaceRenderTarget _surface;
|
||||||
|
private readonly IVulkanPlatformSurface _vulkanPlatformSurface;
|
||||||
|
|
||||||
|
public VulkanRenderTarget(VulkanPlatformInterface vulkanPlatformInterface,
|
||||||
|
IVulkanPlatformSurface vulkanPlatformSurface)
|
||||||
|
{
|
||||||
|
_surface = vulkanPlatformInterface.CreateRenderTarget(vulkanPlatformSurface);
|
||||||
|
_vulkanPlatformSurface = vulkanPlatformSurface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void 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 imageInfo = new GRVkImageInfo()
|
||||||
|
{
|
||||||
|
CurrentQueueFamily = disp.QueueFamilyIndex,
|
||||||
|
Format = _surface.ImageFormat,
|
||||||
|
Image = _surface.Image.Handle,
|
||||||
|
ImageLayout = (uint)_surface.Image.CurrentLayout,
|
||||||
|
ImageTiling = (uint)_surface.Image.Tiling,
|
||||||
|
ImageUsageFlags = _surface.UsageFlags,
|
||||||
|
LevelCount = _surface.MipLevels,
|
||||||
|
SampleCount = 1,
|
||||||
|
Protected = false,
|
||||||
|
Alloc = new GRVkAlloc()
|
||||||
|
{
|
||||||
|
Memory = _surface.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,
|
||||||
|
session.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft,
|
||||||
|
_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 = vulkanSession.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs
Normal file
118
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
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;
|
||||||
|
private readonly long? _maxResourceBytes;
|
||||||
|
private GRContext _grContext;
|
||||||
|
private GRVkBackendContext _grVkBackend;
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
public GRContext GrContext { get => _grContext; set => _grContext = value; }
|
||||||
|
|
||||||
|
public VulkanSkiaGpu(long? maxResourceBytes)
|
||||||
|
{
|
||||||
|
_vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
|
_maxResourceBytes = maxResourceBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
if (_initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialized = true;
|
||||||
|
GRVkGetProcedureAddressDelegate getProc = (string name, IntPtr instanceHandle, IntPtr deviceHandle) =>
|
||||||
|
{
|
||||||
|
IntPtr addr = IntPtr.Zero;
|
||||||
|
|
||||||
|
if (deviceHandle != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
addr = _vulkan.Device.Api.GetDeviceProcAddr(new Device(deviceHandle), name);
|
||||||
|
if (addr != IntPtr.Zero)
|
||||||
|
return addr;
|
||||||
|
|
||||||
|
addr = _vulkan.Device.Api.GetDeviceProcAddr(new Device(_vulkan.Device.Handle), name);
|
||||||
|
|
||||||
|
if (addr != IntPtr.Zero)
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = _vulkan.Device.Api.GetInstanceProcAddr(new Instance(_vulkan.Instance.Handle), name);
|
||||||
|
|
||||||
|
if (addr == IntPtr.Zero)
|
||||||
|
addr = _vulkan.Device.Api.GetInstanceProcAddr(new Instance(instanceHandle), name);
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
};
|
||||||
|
|
||||||
|
_grVkBackend = new GRVkBackendContext()
|
||||||
|
{
|
||||||
|
VkInstance = _vulkan.Device.Handle,
|
||||||
|
VkPhysicalDevice = _vulkan.PhysicalDevice.Handle,
|
||||||
|
VkDevice = _vulkan.Device.Handle,
|
||||||
|
VkQueue = _vulkan.Device.Queue.Handle,
|
||||||
|
GraphicsQueueIndex = _vulkan.PhysicalDevice.QueueFamilyIndex,
|
||||||
|
GetProcedureAddress = getProc
|
||||||
|
};
|
||||||
|
_grContext = GRContext.CreateVulkan(_grVkBackend);
|
||||||
|
if (_maxResourceBytes.HasValue)
|
||||||
|
{
|
||||||
|
_grContext.SetResourceCacheLimit(_maxResourceBytes.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
var vulkanRenderTarget = new VulkanRenderTarget(_vulkan, window);
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
|
||||||
|
vulkanRenderTarget.GrContext = _grContext;
|
||||||
|
|
||||||
|
return vulkanRenderTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISkiaSurface TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSurface.cs
Normal file
58
Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSurface.cs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
|
||||||
|
{
|
||||||
|
internal class VulkanSurfaceRenderTarget : IDisposable
|
||||||
|
{
|
||||||
|
private readonly VulkanPlatformInterface _platformInterface;
|
||||||
|
|
||||||
|
public bool IsCorrupted { get; set; } = true;
|
||||||
|
private readonly Format _format;
|
||||||
|
|
||||||
|
public VulkanImage Image { get; private set; }
|
||||||
|
|
||||||
|
public uint MipLevels => Image.MipLevels;
|
||||||
|
|
||||||
|
public VulkanSurfaceRenderTarget(VulkanPlatformInterface platformInterface, VulkanSurface surface)
|
||||||
|
{
|
||||||
|
_platformInterface = platformInterface;
|
||||||
|
|
||||||
|
Display = VulkanDisplay.CreateDisplay(platformInterface.Instance, platformInterface.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; }
|
||||||
|
|
||||||
|
public VulkanSurface Surface { get; }
|
||||||
|
|
||||||
|
public uint UsageFlags => Image.UsageFlags;
|
||||||
|
|
||||||
|
public PixelSize Size { get; private set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_platformInterface.Device.WaitIdle();
|
||||||
|
DestroyImage();
|
||||||
|
Display?.Dispose();
|
||||||
|
Surface?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VulkanSurfaceRenderingSession BeginDraw(float scaling)
|
||||||
|
{
|
||||||
|
var session = new VulkanSurfaceRenderingSession(Display, _platformInterface.Device, this, scaling);
|
||||||
|
|
||||||
|
if (IsCorrupted)
|
||||||
|
{
|
||||||
|
IsCorrupted = false;
|
||||||
|
DestroyImage();
|
||||||
|
CreateImage();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Image.TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invalidate()
|
||||||
|
{
|
||||||
|
IsCorrupted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateImage()
|
||||||
|
{
|
||||||
|
Size = Display.Size;
|
||||||
|
|
||||||
|
Image = new VulkanImage(_platformInterface.Device, _platformInterface.PhysicalDevice, _platformInterface.Device.CommandBufferPool, ImageFormat, Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DestroyImage()
|
||||||
|
{
|
||||||
|
_platformInterface.Device.WaitIdle();
|
||||||
|
Image?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
177
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs
Normal file
177
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
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();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Dispose()
|
||||||
|
{
|
||||||
|
FreeUsedCommandBuffers();
|
||||||
|
_device.Api.DestroyCommandPool(_device.InternalHandle, _commandPool, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandBuffer AllocateCommandBuffer()
|
||||||
|
{
|
||||||
|
var commandBufferAllocateInfo = new CommandBufferAllocateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.CommandBufferAllocateInfo,
|
||||||
|
CommandPool = _commandPool,
|
||||||
|
CommandBufferCount = 1,
|
||||||
|
Level = CommandBufferLevel.Primary
|
||||||
|
};
|
||||||
|
|
||||||
|
_device.Api.AllocateCommandBuffers(_device.InternalHandle, commandBufferAllocateInfo, out var commandBuffer);
|
||||||
|
|
||||||
|
return commandBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VulkanCommandBuffer CreateCommandBuffer()
|
||||||
|
{
|
||||||
|
return new(_device, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FreeUsedCommandBuffers()
|
||||||
|
{
|
||||||
|
lock (_usedCommandBuffers)
|
||||||
|
{
|
||||||
|
foreach (var usedCommandBuffer in _usedCommandBuffers) usedCommandBuffer.Dispose();
|
||||||
|
|
||||||
|
_usedCommandBuffers.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeCommandBuffer(VulkanCommandBuffer commandBuffer)
|
||||||
|
{
|
||||||
|
lock (_usedCommandBuffers)
|
||||||
|
{
|
||||||
|
_usedCommandBuffers.Add(commandBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VulkanCommandBuffer : IDisposable
|
||||||
|
{
|
||||||
|
private readonly VulkanCommandBufferPool _commandBufferPool;
|
||||||
|
private readonly VulkanDevice _device;
|
||||||
|
private readonly Fence _fence;
|
||||||
|
private bool _hasEnded;
|
||||||
|
private bool _hasStarted;
|
||||||
|
|
||||||
|
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 unsafe void Dispose()
|
||||||
|
{
|
||||||
|
_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, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs
Normal file
67
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
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);
|
||||||
|
|
||||||
|
var vulkanQueue = new VulkanQueue(this, queue);
|
||||||
|
Queue = vulkanQueue;
|
||||||
|
|
||||||
|
PresentQueue = vulkanQueue;
|
||||||
|
|
||||||
|
CommandBufferPool = new VulkanCommandBufferPool(this, physicalDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr Handle => InternalHandle.Handle;
|
||||||
|
|
||||||
|
internal Device InternalHandle { get; }
|
||||||
|
public Vk Api { get; }
|
||||||
|
|
||||||
|
public VulkanQueue Queue { get; private set; }
|
||||||
|
public VulkanQueue PresentQueue { get; }
|
||||||
|
public VulkanCommandBufferPool CommandBufferPool { get; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
WaitIdle();
|
||||||
|
CommandBufferPool?.Dispose();
|
||||||
|
Queue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Submit(SubmitInfo submitInfo, Fence fence = new())
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
Api.QueueSubmit(Queue.InternalHandle, 1, submitInfo, fence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitIdle()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
Api.DeviceWaitIdle(InternalHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void QueueWaitIdle()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
Api.QueueWaitIdle(Queue.InternalHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Lock => _lock;
|
||||||
|
}
|
||||||
|
}
|
406
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs
Normal file
406
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs
Normal file
|
@ -0,0 +1,406 @@
|
||||||
|
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 uint _nextImage;
|
||||||
|
private readonly VulkanSurface _surface;
|
||||||
|
private SurfaceFormatKHR _surfaceFormat;
|
||||||
|
private SwapchainKHR _swapchain;
|
||||||
|
private Extent2D _swapchainExtent;
|
||||||
|
private Image[] _swapchainImages;
|
||||||
|
private VulkanDevice _device { get; }
|
||||||
|
private ImageView[] _swapchainImageViews = new ImageView[0];
|
||||||
|
|
||||||
|
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 unsafe void Dispose()
|
||||||
|
{
|
||||||
|
_device.WaitIdle();
|
||||||
|
_semaphorePair?.Dispose();
|
||||||
|
DestroyCurrentImageViews();
|
||||||
|
_swapchainExtension.DestroySwapchain(_device.InternalHandle, _swapchain, null);
|
||||||
|
CommandBufferPool.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe SwapchainKHR CreateSwapchain(VulkanInstance instance, VulkanDevice device,
|
||||||
|
VulkanPhysicalDevice physicalDevice, VulkanSurface surface, out Extent2D swapchainExtent,
|
||||||
|
VulkanDisplay oldDisplay = null)
|
||||||
|
{
|
||||||
|
if (_swapchainExtension == null)
|
||||||
|
{
|
||||||
|
instance.Api.TryGetDeviceExtension(instance.InternalHandle, device.InternalHandle, out KhrSwapchain extension);
|
||||||
|
|
||||||
|
_swapchainExtension = extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!surface.CanSurfacePresent(physicalDevice))
|
||||||
|
{
|
||||||
|
Thread.Sleep(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanSurface.SurfaceExtension.GetPhysicalDeviceSurfaceCapabilities(physicalDevice.InternalHandle,
|
||||||
|
surface.ApiHandle, out var capabilities);
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
if (capabilities.CurrentExtent.Width != uint.MaxValue)
|
||||||
|
{
|
||||||
|
swapchainExtent = capabilities.CurrentExtent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var surfaceSize = surface.SurfaceSize;
|
||||||
|
|
||||||
|
var width = Math.Max(capabilities.MinImageExtent.Width,
|
||||||
|
Math.Min(capabilities.MaxImageExtent.Width, (uint)surfaceSize.Width));
|
||||||
|
var height = Math.Max(capabilities.MinImageExtent.Height,
|
||||||
|
Math.Min(capabilities.MaxImageExtent.Height, (uint)surfaceSize.Height));
|
||||||
|
|
||||||
|
swapchainExtent = new Extent2D(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
PresentModeKHR presentMode;
|
||||||
|
var modes = presentModes.ToList();
|
||||||
|
|
||||||
|
if (modes.Contains(PresentModeKHR.PresentModeImmediateKhr))
|
||||||
|
presentMode = PresentModeKHR.PresentModeImmediateKhr;
|
||||||
|
else if (modes.Contains(PresentModeKHR.PresentModeMailboxKhr))
|
||||||
|
presentMode = PresentModeKHR.PresentModeMailboxKhr;
|
||||||
|
else
|
||||||
|
presentMode = PresentModeKHR.PresentModeFifoKhr;
|
||||||
|
|
||||||
|
var compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaOpaqueBitKhr;
|
||||||
|
|
||||||
|
if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.CompositeAlphaPostMultipliedBitKhr))
|
||||||
|
{
|
||||||
|
compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaPostMultipliedBitKhr;
|
||||||
|
}
|
||||||
|
else if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.CompositeAlphaPreMultipliedBitKhr))
|
||||||
|
{
|
||||||
|
compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaPreMultipliedBitKhr;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = oldDisplay?._swapchain ?? new SwapchainKHR()
|
||||||
|
};
|
||||||
|
|
||||||
|
_swapchainExtension.CreateSwapchain(device.InternalHandle, swapchainCreateInfo, null, out var swapchain)
|
||||||
|
.ThrowOnError();
|
||||||
|
|
||||||
|
if (oldDisplay != null)
|
||||||
|
{
|
||||||
|
_swapchainExtension.DestroySwapchain(device.InternalHandle, oldDisplay._swapchain, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return swapchain;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal static VulkanDisplay CreateDisplay(VulkanInstance instance, VulkanDevice device,
|
||||||
|
VulkanPhysicalDevice physicalDevice, VulkanSurface surface)
|
||||||
|
{
|
||||||
|
var swapchain = CreateSwapchain(instance, device, physicalDevice, surface, out var extent);
|
||||||
|
|
||||||
|
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 unsafe void DestroyCurrentImageViews()
|
||||||
|
{
|
||||||
|
if (_swapchainImageViews.Length > 0)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _swapchainImageViews.Length; i++)
|
||||||
|
{
|
||||||
|
_instance.Api.DestroyImageView(_device.InternalHandle, _swapchainImageViews[i], null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Recreate()
|
||||||
|
{
|
||||||
|
_device.WaitIdle();
|
||||||
|
_swapchain = CreateSwapchain(_instance, _device, _physicalDevice, _surface, out var extent, this);
|
||||||
|
|
||||||
|
_swapchainExtent = extent;
|
||||||
|
|
||||||
|
CreateSwapchainImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe ImageView CreateSwapchainImageView(Image swapchainImage, Format format)
|
||||||
|
{
|
||||||
|
var componentMapping = new ComponentMapping(
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity,
|
||||||
|
ComponentSwizzle.Identity);
|
||||||
|
|
||||||
|
var aspectFlags = ImageAspectFlags.ImageAspectColorBit;
|
||||||
|
|
||||||
|
var subresourceRange = new ImageSubresourceRange(aspectFlags, 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)
|
||||||
|
{
|
||||||
|
Recreate();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal VulkanCommandBufferPool.VulkanCommandBuffer StartPresentation(VulkanSurfaceRenderTarget renderTarget)
|
||||||
|
{
|
||||||
|
_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)
|
||||||
|
{
|
||||||
|
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
|
||||||
|
renderTarget.Image.InternalHandle.Value, (ImageLayout)renderTarget.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, renderTarget.Image.InternalHandle.Value,
|
||||||
|
ImageLayout.TransferSrcOptimal,
|
||||||
|
_swapchainImages[_nextImage],
|
||||||
|
ImageLayout.TransferDstOptimal,
|
||||||
|
1,
|
||||||
|
srcBlitRegion,
|
||||||
|
Filter.Linear);
|
||||||
|
|
||||||
|
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
|
||||||
|
renderTarget.Image.InternalHandle.Value, ImageLayout.TransferSrcOptimal,
|
||||||
|
AccessFlags.AccessTransferReadBit,
|
||||||
|
(ImageLayout)renderTarget.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(
|
||||||
|
new[] { _semaphorePair.ImageAvailableSemaphore },
|
||||||
|
new[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit },
|
||||||
|
new[] { _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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
167
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs
Normal file
167
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
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 TransitionLayout(uint destinationLayout, uint destinationAccessFlags)
|
||||||
|
{
|
||||||
|
TransitionLayout((ImageLayout)destinationLayout, (AccessFlags)destinationAccessFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Dispose()
|
||||||
|
{
|
||||||
|
_device.Api.DestroyImageView(_device.InternalHandle, _imageView.Value, null);
|
||||||
|
_device.Api.DestroyImage(_device.InternalHandle, InternalHandle.Value, null);
|
||||||
|
_device.Api.FreeMemory(_device.InternalHandle, _imageMemory, null);
|
||||||
|
|
||||||
|
_imageView = default;
|
||||||
|
InternalHandle = default;
|
||||||
|
_imageMemory = default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs
Normal file
138
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
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 IList<string> RequiredInstanceExtensions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var extensions = new List<string> { "VK_KHR_surface" };
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
extensions.Add("VK_KHR_xlib_surface");
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
extensions.Add("VK_KHR_win32_surface");
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Dispose()
|
||||||
|
{
|
||||||
|
Api?.DestroyInstance(InternalHandle, null);
|
||||||
|
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 = new Version32((uint)options.VulkanVersion.Major, (uint)options.VulkanVersion.Minor,
|
||||||
|
(uint)options.VulkanVersion.Build),
|
||||||
|
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);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanMemoryHelper.cs
Normal file
59
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanMemoryHelper.cs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs
Normal file
48
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
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 the Vulkan API version to use
|
||||||
|
/// </summary>
|
||||||
|
public Version VulkanVersion{ get; set; } = new Version(1, 1, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies additional extensions to enable if available on the instance
|
||||||
|
/// </summary>
|
||||||
|
public IList<string> InstanceExtensions { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies layers to enable if available on the instance
|
||||||
|
/// </summary>
|
||||||
|
public IList<string> EnabledLayers { get; set; } = new List<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 uint? PreferredDevice { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Max number of device queues to request
|
||||||
|
/// </summary>
|
||||||
|
public uint MaxQueueCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
203
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPhysicalDevice.cs
Normal file
203
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPhysicalDevice.cs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
|
using Silk.NET.Core;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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 Version ApiVersion { get; }
|
||||||
|
|
||||||
|
internal static unsafe VulkanPhysicalDevice FindSuitablePhysicalDevice(VulkanInstance instance,
|
||||||
|
VulkanSurface surface, bool preferDiscreteGpu, uint? 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
var physicalDeviceProperties = new Dictionary<PhysicalDevice, PhysicalDeviceProperties>();
|
||||||
|
|
||||||
|
foreach (var physicalDevice in physicalDevices)
|
||||||
|
{
|
||||||
|
instance.Api.GetPhysicalDeviceProperties(physicalDevice, out var properties);
|
||||||
|
physicalDeviceProperties.Add(physicalDevice, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferredDevice.HasValue && preferredDevice != 0)
|
||||||
|
{
|
||||||
|
var physicalDevice = physicalDeviceProperties.FirstOrDefault(x => x.Value.DeviceID == preferredDevice);
|
||||||
|
if (physicalDevice.Key.Handle != 0 && IsSuitableDevice(instance.Api, physicalDevice.Key,
|
||||||
|
physicalDevice.Value, surface.ApiHandle, out var queueCount,
|
||||||
|
out var queueFamilyIndex))
|
||||||
|
return new VulkanPhysicalDevice(physicalDevice.Key, instance.Api, queueCount, queueFamilyIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferDiscreteGpu)
|
||||||
|
{
|
||||||
|
var discreteGpus = physicalDeviceProperties.Where(p => p.Value.DeviceType == PhysicalDeviceType.DiscreteGpu);
|
||||||
|
|
||||||
|
foreach (var gpu in discreteGpus)
|
||||||
|
{
|
||||||
|
if (IsSuitableDevice(
|
||||||
|
instance.Api,
|
||||||
|
gpu.Key,
|
||||||
|
gpu.Value,
|
||||||
|
surface.ApiHandle,
|
||||||
|
out var queueCount,
|
||||||
|
out var queueFamilyIndex))
|
||||||
|
{
|
||||||
|
return new VulkanPhysicalDevice(gpu.Key, instance.Api, queueCount, queueFamilyIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
physicalDeviceProperties.Remove(gpu.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var physicalDevice in physicalDeviceProperties)
|
||||||
|
if (IsSuitableDevice(
|
||||||
|
instance.Api,
|
||||||
|
physicalDevice.Key,
|
||||||
|
physicalDevice.Value,
|
||||||
|
surface.ApiHandle,
|
||||||
|
out var queueCount,
|
||||||
|
out var queueFamilyIndex))
|
||||||
|
{
|
||||||
|
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 unsafe 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs
Normal file
84
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
using System;
|
||||||
|
using Avalonia;
|
||||||
|
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
|
|
||||||
|
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 VulkanDevice Device { get; set; }
|
||||||
|
public Vk Api { get; private set; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Device?.Dispose();
|
||||||
|
Instance?.Dispose();
|
||||||
|
Api?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VulkanPlatformInterface TryCreate()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_options = AvaloniaLocator.Current.GetService<VulkanOptions>() ?? new VulkanOptions();
|
||||||
|
|
||||||
|
var instance = VulkanInstance.Create(_options);
|
||||||
|
|
||||||
|
return new VulkanPlatformInterface(instance);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Device == null)
|
||||||
|
{
|
||||||
|
PhysicalDevice = VulkanPhysicalDevice.FindSuitablePhysicalDevice(Instance, surface, _options.PreferDiscreteGpu, _options.PreferredDevice);
|
||||||
|
var device = VulkanInitialization.CreateDevice(Instance.Api,
|
||||||
|
PhysicalDevice.InternalHandle,
|
||||||
|
PhysicalDevice.QueueFamilyIndex,
|
||||||
|
VulkanInitialization.GetSupportedExtensions(Instance.Api, PhysicalDevice.InternalHandle),
|
||||||
|
PhysicalDevice.QueueCount);
|
||||||
|
|
||||||
|
Device = new VulkanDevice(device, PhysicalDevice, Instance.Api);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
surface.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VulkanSurfaceRenderTarget(this, surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanQueue.cs
Normal file
18
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanQueue.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
32
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSemaphorePair.cs
Normal file
32
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSemaphorePair.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurface.cs
Normal file
77
Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurface.cs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
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 unsafe void Dispose()
|
||||||
|
{
|
||||||
|
SurfaceExtension.DestroySurface(_instance.InternalHandle, ApiHandle, null);
|
||||||
|
_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 unsafe SurfaceFormatKHR GetSurfaceFormat(VulkanPhysicalDevice physicalDevice)
|
||||||
|
{
|
||||||
|
uint surfaceFormatsCount;
|
||||||
|
|
||||||
|
SurfaceExtension.GetPhysicalDeviceSurfaceFormats(physicalDevice.InternalHandle, ApiHandle,
|
||||||
|
&surfaceFormatsCount, null);
|
||||||
|
|
||||||
|
var surfaceFormats = new SurfaceFormatKHR[surfaceFormatsCount];
|
||||||
|
|
||||||
|
fixed (SurfaceFormatKHR* pSurfaceFormats = surfaceFormats)
|
||||||
|
{
|
||||||
|
SurfaceExtension.GetPhysicalDeviceSurfaceFormats(physicalDevice.InternalHandle, ApiHandle,
|
||||||
|
&surfaceFormatsCount, pSurfaceFormats);
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
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;
|
||||||
|
private VulkanCommandBufferPool.VulkanCommandBuffer _commandBuffer;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
public bool IsYFlipped { get; } = true;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_commandBuffer = Display.StartPresentation(_renderTarget);
|
||||||
|
|
||||||
|
Display.BlitImageToCurrentImage(_renderTarget, _commandBuffer.InternalHandle);
|
||||||
|
|
||||||
|
Display.EndPresentation(_commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Begin()
|
||||||
|
{
|
||||||
|
if (!Display.EnsureSwapchainAvailable())
|
||||||
|
_renderTarget.Invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
192
Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs
Normal file
192
Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
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;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}).Wait();
|
||||||
|
|
||||||
|
if (_fence != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
GL.DeleteSync(_fence);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
|
||||||
|
|
||||||
|
QueueRender();
|
||||||
|
|
||||||
|
_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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,65 +2,45 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.OpenGL;
|
|
||||||
using Avalonia.Platform;
|
|
||||||
using Avalonia.Rendering.SceneGraph;
|
using Avalonia.Rendering.SceneGraph;
|
||||||
using Avalonia.Skia;
|
|
||||||
using Avalonia.Threading;
|
|
||||||
using OpenTK.Graphics.OpenGL;
|
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using SkiaSharp;
|
|
||||||
using SPB.Graphics;
|
|
||||||
using SPB.Graphics.OpenGL;
|
|
||||||
using SPB.Platform;
|
|
||||||
using SPB.Windowing;
|
using SPB.Windowing;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Controls
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
{
|
{
|
||||||
public class RendererControl : Control
|
public abstract class RendererControl : Control
|
||||||
{
|
{
|
||||||
private int _image;
|
protected object _image;
|
||||||
|
|
||||||
static RendererControl()
|
static RendererControl()
|
||||||
{
|
{
|
||||||
AffectsRender<RendererControl>(ImageProperty);
|
AffectsRender<RendererControl>(ImageProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly static StyledProperty<int> ImageProperty =
|
public readonly static StyledProperty<object> ImageProperty =
|
||||||
AvaloniaProperty.Register<RendererControl, int>(nameof(Image), 0, inherits: true, defaultBindingMode: BindingMode.TwoWay);
|
AvaloniaProperty.Register<RendererControl, object>(nameof(Image), 0, inherits: true, defaultBindingMode: BindingMode.TwoWay);
|
||||||
|
|
||||||
protected int Image
|
protected object Image
|
||||||
{
|
{
|
||||||
get => _image;
|
get => _image;
|
||||||
set => SetAndRaise(ImageProperty, ref _image, value);
|
set => SetAndRaise(ImageProperty, ref _image, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler<EventArgs> GlInitialized;
|
public event EventHandler<EventArgs> RendererInitialized;
|
||||||
public event EventHandler<Size> SizeChanged;
|
public event EventHandler<Size> SizeChanged;
|
||||||
|
|
||||||
protected Size RenderSize { get; private set; }
|
protected Size RenderSize { get; private set; }
|
||||||
public bool IsStarted { get; private set; }
|
public bool IsStarted { get; private set; }
|
||||||
|
|
||||||
public int Major { get; }
|
|
||||||
public int Minor { get; }
|
|
||||||
public GraphicsDebugLevel DebugLevel { get; }
|
public GraphicsDebugLevel DebugLevel { get; }
|
||||||
public OpenGLContextBase GameContext { get; set; }
|
|
||||||
|
|
||||||
public static OpenGLContextBase PrimaryContext => AvaloniaLocator.Current.GetService<IPlatformOpenGlInterface>().PrimaryContext.AsOpenGLContextBase();
|
|
||||||
|
|
||||||
private SwappableNativeWindowBase _gameBackgroundWindow;
|
|
||||||
|
|
||||||
private bool _isInitialized;
|
private bool _isInitialized;
|
||||||
|
|
||||||
private IntPtr _fence;
|
protected ICustomDrawOperation DrawOperation { get; private set; }
|
||||||
|
|
||||||
private GlDrawOperation _glDrawOperation;
|
public RendererControl(GraphicsDebugLevel graphicsDebugLevel)
|
||||||
|
|
||||||
public RendererControl(int major, int minor, GraphicsDebugLevel graphicsDebugLevel)
|
|
||||||
{
|
{
|
||||||
Major = major;
|
|
||||||
Minor = minor;
|
|
||||||
DebugLevel = graphicsDebugLevel;
|
DebugLevel = graphicsDebugLevel;
|
||||||
IObservable<Rect> resizeObservable = this.GetObservable(BoundsProperty);
|
IObservable<Rect> resizeObservable = this.GetObservable(BoundsProperty);
|
||||||
|
|
||||||
|
@ -69,7 +49,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
Focusable = true;
|
Focusable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Resized(Rect rect)
|
protected void Resized(Rect rect)
|
||||||
{
|
{
|
||||||
SizeChanged?.Invoke(this, rect.Size);
|
SizeChanged?.Invoke(this, rect.Size);
|
||||||
|
|
||||||
|
@ -77,37 +57,40 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
{
|
{
|
||||||
RenderSize = rect.Size * VisualRoot.RenderScaling;
|
RenderSize = rect.Size * VisualRoot.RenderScaling;
|
||||||
|
|
||||||
_glDrawOperation?.Dispose();
|
DrawOperation?.Dispose();
|
||||||
_glDrawOperation = new GlDrawOperation(this);
|
DrawOperation = CreateDrawOperation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract ICustomDrawOperation CreateDrawOperation();
|
||||||
|
protected abstract void CreateWindow();
|
||||||
|
|
||||||
public override void Render(DrawingContext context)
|
public override void Render(DrawingContext context)
|
||||||
{
|
{
|
||||||
if (!_isInitialized)
|
if (!_isInitialized)
|
||||||
{
|
{
|
||||||
CreateWindow();
|
CreateWindow();
|
||||||
|
|
||||||
OnGlInitialized();
|
OnRendererInitialized();
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameContext == null || !IsStarted || Image == 0)
|
if (!IsStarted || Image == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_glDrawOperation != null)
|
if (DrawOperation != null)
|
||||||
{
|
{
|
||||||
context.Custom(_glDrawOperation);
|
context.Custom(DrawOperation);
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Render(context);
|
base.Render(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void OnGlInitialized()
|
protected void OnRendererInitialized()
|
||||||
{
|
{
|
||||||
GlInitialized?.Invoke(this, EventArgs.Empty);
|
RendererInitialized?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueRender()
|
public void QueueRender()
|
||||||
|
@ -115,24 +98,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
Program.RenderTimer.TickNow();
|
Program.RenderTimer.TickNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Present(object image)
|
internal abstract void Present(object image);
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
Image = (int)image;
|
|
||||||
}).Wait();
|
|
||||||
|
|
||||||
if (_fence != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
GL.DeleteSync(_fence);
|
|
||||||
}
|
|
||||||
|
|
||||||
_fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
|
|
||||||
|
|
||||||
QueueRender();
|
|
||||||
|
|
||||||
_gameBackgroundWindow.SwapBuffers();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Start()
|
internal void Start()
|
||||||
{
|
{
|
||||||
|
@ -145,132 +111,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
IsStarted = false;
|
IsStarted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DestroyBackgroundContext()
|
public abstract void DestroyBackgroundContext();
|
||||||
{
|
internal abstract void MakeCurrent();
|
||||||
_image = 0;
|
internal abstract void MakeCurrent(SwappableNativeWindowBase window);
|
||||||
|
|
||||||
if (_fence != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
_glDrawOperation.Dispose();
|
|
||||||
GL.DeleteSync(_fence);
|
|
||||||
}
|
|
||||||
|
|
||||||
GlDrawOperation.DeleteFramebuffer();
|
|
||||||
|
|
||||||
GameContext?.Dispose();
|
|
||||||
|
|
||||||
_gameBackgroundWindow?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void MakeCurrent()
|
|
||||||
{
|
|
||||||
GameContext.MakeCurrent(_gameBackgroundWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void MakeCurrent(SwappableNativeWindowBase window)
|
|
||||||
{
|
|
||||||
GameContext.MakeCurrent(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class GlDrawOperation : ICustomDrawOperation
|
|
||||||
{
|
|
||||||
private static int _framebuffer;
|
|
||||||
|
|
||||||
public Rect Bounds { get; }
|
|
||||||
|
|
||||||
private readonly RendererControl _control;
|
|
||||||
|
|
||||||
public GlDrawOperation(RendererControl 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 == 0)
|
|
||||||
{
|
|
||||||
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, 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
153
Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs
Normal file
153
Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
using Avalonia;
|
||||||
|
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.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
{
|
||||||
|
internal class VulkanRendererControl : RendererControl
|
||||||
|
{
|
||||||
|
private VulkanPlatformInterface _platformInterface;
|
||||||
|
|
||||||
|
public VulkanRendererControl(GraphicsDebugLevel graphicsDebugLevel) : base(graphicsDebugLevel)
|
||||||
|
{
|
||||||
|
_platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DestroyBackgroundContext()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ICustomDrawOperation CreateDrawOperation()
|
||||||
|
{
|
||||||
|
return new VulkanDrawOperation(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CreateWindow()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void MakeCurrent()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void MakeCurrent(SwappableNativeWindowBase window)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Present(object image)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
Image = image;
|
||||||
|
}).Wait();
|
||||||
|
|
||||||
|
QueueRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VulkanDrawOperation : ICustomDrawOperation
|
||||||
|
{
|
||||||
|
public Rect Bounds { get; }
|
||||||
|
|
||||||
|
private readonly VulkanRendererControl _control;
|
||||||
|
|
||||||
|
public VulkanDrawOperation(VulkanRendererControl control)
|
||||||
|
{
|
||||||
|
_control = control;
|
||||||
|
Bounds = _control.Bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 void Render(IDrawingContextImpl context)
|
||||||
|
{
|
||||||
|
if (_control.Image == null || _control.RenderSize.Width == 0 || _control.RenderSize.Height == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var image = (PresentImageInfo)_control.Image;
|
||||||
|
|
||||||
|
if (context is not ISkiaDrawingContextImpl skiaDrawingContextImpl)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_control._platformInterface.Device.QueueWaitIdle();
|
||||||
|
|
||||||
|
var gpu = AvaloniaLocator.Current.GetService<VulkanSkiaGpu>();
|
||||||
|
|
||||||
|
var imageInfo = new GRVkImageInfo()
|
||||||
|
{
|
||||||
|
CurrentQueueFamily = _control._platformInterface.PhysicalDevice.QueueFamilyIndex,
|
||||||
|
Format = (uint)Format.R8G8B8A8Unorm,
|
||||||
|
Image = image.Image.Handle,
|
||||||
|
ImageLayout = (uint)ImageLayout.ColorAttachmentOptimal,
|
||||||
|
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)_control.RenderSize.Width,
|
||||||
|
(int)_control.RenderSize.Height,
|
||||||
|
1,
|
||||||
|
imageInfo);
|
||||||
|
|
||||||
|
using var surface = SKSurface.Create(
|
||||||
|
gpu.GrContext,
|
||||||
|
backendTexture,
|
||||||
|
GRSurfaceOrigin.TopLeft,
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,7 +61,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
public AppHost AppHost { get; private set; }
|
public AppHost AppHost { get; private set; }
|
||||||
public InputManager InputManager { get; private set; }
|
public InputManager InputManager { get; private set; }
|
||||||
|
|
||||||
public RendererControl GlRenderer { get; private set; }
|
public RendererControl RendererControl { get; private set; }
|
||||||
public ContentControl ContentFrame { get; private set; }
|
public ContentControl ContentFrame { get; private set; }
|
||||||
public TextBlock LoadStatus { get; private set; }
|
public TextBlock LoadStatus { get; private set; }
|
||||||
public TextBlock FirmwareStatus { get; private set; }
|
public TextBlock FirmwareStatus { get; private set; }
|
||||||
|
@ -257,8 +257,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
|
|
||||||
_mainViewContent = ContentFrame.Content as Control;
|
_mainViewContent = ContentFrame.Content as Control;
|
||||||
|
|
||||||
GlRenderer = new RendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
RendererControl = Program.UseVulkan ? new VulkanRendererControl(ConfigurationState.Instance.Logger.GraphicsDebugLevel) : new OpenGLRendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
||||||
AppHost = new AppHost(GlRenderer, 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)
|
||||||
{
|
{
|
||||||
|
@ -282,7 +282,7 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
|
|
||||||
private void InitializeGame()
|
private void InitializeGame()
|
||||||
{
|
{
|
||||||
GlRenderer.GlInitialized += GlRenderer_Created;
|
RendererControl.RendererInitialized += GlRenderer_Created;
|
||||||
|
|
||||||
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
||||||
AppHost.AppExit += AppHost_AppExit;
|
AppHost.AppExit += AppHost_AppExit;
|
||||||
|
@ -322,14 +322,14 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
ContentFrame.Content = GlRenderer;
|
ContentFrame.Content = RendererControl;
|
||||||
|
|
||||||
if (startFullscreen && WindowState != WindowState.FullScreen)
|
if (startFullscreen && WindowState != WindowState.FullScreen)
|
||||||
{
|
{
|
||||||
ViewModel.ToggleFullscreen();
|
ViewModel.ToggleFullscreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
GlRenderer.Focus();
|
RendererControl.Focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,8 +380,8 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
|
|
||||||
HandleRelaunch();
|
HandleRelaunch();
|
||||||
});
|
});
|
||||||
GlRenderer.GlInitialized -= GlRenderer_Created;
|
RendererControl.RendererInitialized -= GlRenderer_Created;
|
||||||
GlRenderer = null;
|
RendererControl = null;
|
||||||
|
|
||||||
ViewModel.SelectedIcon = null;
|
ViewModel.SelectedIcon = null;
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ namespace Ryujinx.Common.System
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double GetWindowScaleFactor()
|
public static double GetActualScaleFactor()
|
||||||
{
|
{
|
||||||
double userDpiScale = 96.0;
|
double userDpiScale = 96.0;
|
||||||
|
|
||||||
|
@ -84,6 +84,13 @@ namespace Ryujinx.Common.System
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}");
|
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return userDpiScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double GetWindowScaleFactor()
|
||||||
|
{
|
||||||
|
double userDpiScale = GetActualScaleFactor();
|
||||||
|
|
||||||
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
|
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
renderer.ScreenCaptured += (object sender, ScreenCaptureImageInfo info) => ScreenCaptured?.Invoke(this, info);
|
renderer.ScreenCaptured += (object sender, ScreenCaptureImageInfo info) => ScreenCaptured?.Invoke(this, info);
|
||||||
|
|
||||||
Pipeline = new ThreadedPipeline(this, renderer.Pipeline);
|
Pipeline = new ThreadedPipeline(this, renderer.Pipeline);
|
||||||
Window = new ThreadedWindow(this, renderer.Window);
|
Window = new ThreadedWindow(this, renderer);
|
||||||
Buffers = new BufferMap();
|
Buffers = new BufferMap();
|
||||||
Sync = new SyncMap();
|
Sync = new SyncMap();
|
||||||
Programs = new ProgramQueue(renderer);
|
Programs = new ProgramQueue(renderer);
|
||||||
|
|
|
@ -8,12 +8,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
public class ThreadedWindow : IWindow
|
public class ThreadedWindow : IWindow
|
||||||
{
|
{
|
||||||
private ThreadedRenderer _renderer;
|
private ThreadedRenderer _renderer;
|
||||||
private IWindow _impl;
|
private IRenderer _impl;
|
||||||
|
|
||||||
public ThreadedWindow(ThreadedRenderer renderer, IWindow window)
|
public ThreadedWindow(ThreadedRenderer renderer, IRenderer impl)
|
||||||
{
|
{
|
||||||
_renderer = renderer;
|
_renderer = renderer;
|
||||||
_impl = window;
|
_impl = impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback)
|
public void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback)
|
||||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
|
|
||||||
public void SetSize(int width, int height)
|
public void SetSize(int width, int height)
|
||||||
{
|
{
|
||||||
_impl.SetSize(width, height);
|
_impl.Window.SetSize(width, height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
365
Ryujinx.Graphics.Vulkan/ImageWindow.cs
Normal file
365
Ryujinx.Graphics.Vulkan/ImageWindow.cs
Normal file
|
@ -0,0 +1,365 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
private const int ImageCount = 5;
|
||||||
|
private const int SurfaceWidth = 1280;
|
||||||
|
private const int SurfaceHeight = 720;
|
||||||
|
|
||||||
|
private readonly VulkanGraphicsDevice _gd;
|
||||||
|
private readonly PhysicalDevice _physicalDevice;
|
||||||
|
private readonly Device _device;
|
||||||
|
|
||||||
|
private Auto<DisposableImage>[] _images;
|
||||||
|
private Auto<DisposableImageView>[] _imageViews;
|
||||||
|
private Auto<MemoryAllocation>[] _imageAllocationAuto;
|
||||||
|
private ulong[] _imageSizes;
|
||||||
|
private ulong[] _imageOffsets;
|
||||||
|
|
||||||
|
private Semaphore _imageAvailableSemaphore;
|
||||||
|
private Semaphore _renderFinishedSemaphore;
|
||||||
|
|
||||||
|
private int _width = SurfaceWidth;
|
||||||
|
private int _height = SurfaceHeight;
|
||||||
|
private VkFormat _format;
|
||||||
|
private bool _recreateImages;
|
||||||
|
private int _nextImage;
|
||||||
|
|
||||||
|
internal new bool ScreenCaptureRequested { get; set; }
|
||||||
|
|
||||||
|
public unsafe ImageWindow(VulkanGraphicsDevice gd, PhysicalDevice physicalDevice, Device device)
|
||||||
|
{
|
||||||
|
_gd = gd;
|
||||||
|
_physicalDevice = physicalDevice;
|
||||||
|
_device = device;
|
||||||
|
|
||||||
|
_format = VkFormat.R8G8B8A8Unorm;
|
||||||
|
|
||||||
|
_images = new Auto<DisposableImage>[ImageCount];
|
||||||
|
_imageAllocationAuto = new Auto<MemoryAllocation>[ImageCount];
|
||||||
|
_imageSizes = new ulong[ImageCount];
|
||||||
|
_imageOffsets = new ulong[ImageCount];
|
||||||
|
|
||||||
|
CreateImages();
|
||||||
|
|
||||||
|
var semaphoreCreateInfo = new SemaphoreCreateInfo()
|
||||||
|
{
|
||||||
|
SType = StructureType.SemaphoreCreateInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _imageAvailableSemaphore).ThrowOnError();
|
||||||
|
gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _renderFinishedSemaphore).ThrowOnError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecreateImages()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ImageCount; i++)
|
||||||
|
{
|
||||||
|
_imageViews[i]?.Dispose();
|
||||||
|
_imageAllocationAuto[i]?.Dispose();
|
||||||
|
_images[i]?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateImages()
|
||||||
|
{
|
||||||
|
_imageViews = new Auto<DisposableImageView>[ImageCount];
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
var cbs = _gd.CommandBufferPool.Rent();
|
||||||
|
for (int i = 0; i < _images.Length; i++)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
_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.ColorAttachmentOptimal);
|
||||||
|
}
|
||||||
|
|
||||||
|
_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 aspectFlags = ImageAspectFlags.ImageAspectColorBit;
|
||||||
|
|
||||||
|
var subresourceRange = new ImageSubresourceRange(aspectFlags, 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.ColorAttachmentOptimal,
|
||||||
|
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.ColorAttachmentOptimal);
|
||||||
|
|
||||||
|
_gd.CommandBufferPool.Return(
|
||||||
|
cbs,
|
||||||
|
null,
|
||||||
|
new[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit },
|
||||||
|
null);
|
||||||
|
|
||||||
|
var memory = _imageAllocationAuto[_nextImage].GetUnsafe().Memory;
|
||||||
|
var presentInfo = new PresentImageInfo(image.GetUnsafe().Value, memory, _imageSizes[_nextImage], _imageOffsets[_nextImage], _renderFinishedSemaphore, _imageAvailableSemaphore);
|
||||||
|
|
||||||
|
swapBuffersCallback(presentInfo);
|
||||||
|
|
||||||
|
_nextImage %= 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
|
||||||
|
{
|
||||||
|
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphore, null);
|
||||||
|
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphore, null);
|
||||||
|
|
||||||
|
for (int i = 0; i < ImageCount; i++)
|
||||||
|
{
|
||||||
|
_imageViews[i]?.Dispose();
|
||||||
|
_imageAllocationAuto[i]?.Dispose();
|
||||||
|
_images[i]?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PresentImageInfo
|
||||||
|
{
|
||||||
|
public Image Image { get; }
|
||||||
|
public DeviceMemory Memory { get; }
|
||||||
|
public ulong MemorySize { get; set; }
|
||||||
|
public ulong MemoryOffset { get; set; }
|
||||||
|
public Semaphore ReadySemaphore { get; }
|
||||||
|
public Semaphore AvailableSemaphore { get; }
|
||||||
|
|
||||||
|
public PresentImageInfo(Image image, DeviceMemory memory, ulong memorySize, ulong memoryOffset, Semaphore readySemaphore, Semaphore availableSemaphore)
|
||||||
|
{
|
||||||
|
this.Image = image;
|
||||||
|
this.Memory = memory;
|
||||||
|
this.MemorySize = memorySize;
|
||||||
|
this.MemoryOffset = memoryOffset;
|
||||||
|
this.ReadySemaphore = readySemaphore;
|
||||||
|
this.AvailableSemaphore = availableSemaphore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private SurfaceKHR _surface;
|
private SurfaceKHR _surface;
|
||||||
private PhysicalDevice _physicalDevice;
|
private PhysicalDevice _physicalDevice;
|
||||||
private Device _device;
|
private Device _device;
|
||||||
private Window _window;
|
private uint _queueFamilyIndex;
|
||||||
|
private WindowBase _window;
|
||||||
|
|
||||||
internal FormatCapabilities FormatCapabilities { get; private set; }
|
internal FormatCapabilities FormatCapabilities { get; private set; }
|
||||||
internal HardwareCapabilities Capabilities;
|
internal HardwareCapabilities Capabilities;
|
||||||
|
@ -42,7 +43,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
internal bool SupportsSubgroupSizeControl { get; private set; }
|
internal bool SupportsSubgroupSizeControl { 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; }
|
||||||
|
@ -96,32 +97,27 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
Samplers = new HashSet<SamplerHolder>();
|
Samplers = new HashSet<SamplerHolder>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void SetupContext(GraphicsDebugLevel logLevel)
|
public VulkanGraphicsDevice(Instance instance, Device device, PhysicalDevice physicalDevice, Queue queue, uint queueFamilyIndex, object lockObject)
|
||||||
{
|
{
|
||||||
var api = Vk.GetApi();
|
_instance = instance;
|
||||||
|
_physicalDevice = physicalDevice;
|
||||||
|
_device = device;
|
||||||
|
_queueFamilyIndex = queueFamilyIndex;
|
||||||
|
|
||||||
Api = api;
|
Queue = queue;
|
||||||
|
QueueLock = lockObject;
|
||||||
|
|
||||||
_instance = VulkanInitialization.CreateInstance(api, logLevel, GetRequiredExtensions(), out ExtDebugReport debugReport, out _debugReportCallback);
|
IsOffScreen = true;
|
||||||
|
Shaders = new HashSet<ShaderCollection>();
|
||||||
|
Textures = new HashSet<ITexture>();
|
||||||
|
Samplers = new HashSet<SamplerHolder>();
|
||||||
|
}
|
||||||
|
|
||||||
DebugReportApi = debugReport;
|
private unsafe void LoadFeatures(string[] supportedExtensions, uint maxQueueCount, uint queueFamilyIndex)
|
||||||
|
{
|
||||||
if (api.TryGetInstanceExtension(_instance, out KhrSurface surfaceApi))
|
FormatCapabilities = new FormatCapabilities(Api, _physicalDevice);
|
||||||
{
|
|
||||||
SurfaceApi = surfaceApi;
|
|
||||||
}
|
|
||||||
|
|
||||||
_surface = GetSurface(_instance, api);
|
|
||||||
_physicalDevice = VulkanInitialization.FindSuitablePhysicalDevice(api, _instance, _surface);
|
|
||||||
|
|
||||||
FormatCapabilities = new FormatCapabilities(api, _physicalDevice);
|
|
||||||
|
|
||||||
var queueFamilyIndex = VulkanInitialization.FindSuitableQueueFamily(api, _physicalDevice, _surface, out uint maxQueueCount);
|
|
||||||
var supportedExtensions = VulkanInitialization.GetSupportedExtensions(api, _physicalDevice);
|
|
||||||
var supportedFeatures = api.GetPhysicalDeviceFeature(_physicalDevice);
|
|
||||||
|
|
||||||
_device = VulkanInitialization.CreateDevice(api, _physicalDevice, queueFamilyIndex, supportedExtensions, maxQueueCount);
|
|
||||||
|
|
||||||
|
var supportedFeatures = Api.GetPhysicalDeviceFeature(_physicalDevice);
|
||||||
SupportsIndexTypeUint8 = supportedExtensions.Contains("VK_EXT_index_type_uint8");
|
SupportsIndexTypeUint8 = supportedExtensions.Contains("VK_EXT_index_type_uint8");
|
||||||
SupportsCustomBorderColor = supportedExtensions.Contains("VK_EXT_custom_border_color");
|
SupportsCustomBorderColor = supportedExtensions.Contains("VK_EXT_custom_border_color");
|
||||||
SupportsIndirectParameters = supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName);
|
SupportsIndirectParameters = supportedExtensions.Contains(KhrDrawIndirectCount.ExtensionName);
|
||||||
|
@ -129,38 +125,29 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
SupportsGeometryShaderPassthrough = supportedExtensions.Contains("VK_NV_geometry_shader_passthrough");
|
SupportsGeometryShaderPassthrough = supportedExtensions.Contains("VK_NV_geometry_shader_passthrough");
|
||||||
SupportsSubgroupSizeControl = supportedExtensions.Contains("VK_EXT_subgroup_size_control");
|
SupportsSubgroupSizeControl = supportedExtensions.Contains("VK_EXT_subgroup_size_control");
|
||||||
|
|
||||||
if (api.TryGetDeviceExtension(_instance, _device, out KhrSwapchain swapchainApi))
|
if (Api.TryGetDeviceExtension(_instance, _device, out ExtConditionalRendering conditionalRenderingApi))
|
||||||
{
|
|
||||||
SwapchainApi = swapchainApi;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (api.TryGetDeviceExtension(_instance, _device, out ExtConditionalRendering conditionalRenderingApi))
|
|
||||||
{
|
{
|
||||||
ConditionalRenderingApi = conditionalRenderingApi;
|
ConditionalRenderingApi = conditionalRenderingApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (api.TryGetDeviceExtension(_instance, _device, out ExtExtendedDynamicState extendedDynamicStateApi))
|
if (Api.TryGetDeviceExtension(_instance, _device, out ExtExtendedDynamicState extendedDynamicStateApi))
|
||||||
{
|
{
|
||||||
ExtendedDynamicStateApi = extendedDynamicStateApi;
|
ExtendedDynamicStateApi = extendedDynamicStateApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (api.TryGetDeviceExtension(_instance, _device, out ExtTransformFeedback transformFeedbackApi))
|
if (Api.TryGetDeviceExtension(_instance, _device, out ExtTransformFeedback transformFeedbackApi))
|
||||||
{
|
{
|
||||||
TransformFeedbackApi = transformFeedbackApi;
|
TransformFeedbackApi = transformFeedbackApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (api.TryGetDeviceExtension(_instance, _device, out KhrDrawIndirectCount drawIndirectCountApi))
|
if (Api.TryGetDeviceExtension(_instance, _device, out KhrDrawIndirectCount drawIndirectCountApi))
|
||||||
{
|
{
|
||||||
DrawIndirectCountApi = drawIndirectCountApi;
|
DrawIndirectCountApi = drawIndirectCountApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
api.GetDeviceQueue(_device, queueFamilyIndex, 0, out var queue);
|
|
||||||
Queue = queue;
|
|
||||||
QueueLock = new object();
|
|
||||||
|
|
||||||
if (maxQueueCount >= 2)
|
if (maxQueueCount >= 2)
|
||||||
{
|
{
|
||||||
api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue);
|
Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue);
|
||||||
BackgroundQueue = backgroundQueue;
|
BackgroundQueue = backgroundQueue;
|
||||||
BackgroundQueueLock = new object();
|
BackgroundQueueLock = new object();
|
||||||
}
|
}
|
||||||
|
@ -226,9 +213,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
ref var properties = ref properties2.Properties;
|
ref var properties = ref properties2.Properties;
|
||||||
|
|
||||||
MemoryAllocator = new MemoryAllocator(api, _device, properties.Limits.MaxMemoryAllocationCount);
|
MemoryAllocator = new MemoryAllocator(Api, _device, properties.Limits.MaxMemoryAllocationCount);
|
||||||
|
|
||||||
CommandBufferPool = VulkanInitialization.CreateCommandBufferPool(api, _device, queue, QueueLock, queueFamilyIndex);
|
CommandBufferPool = VulkanInitialization.CreateCommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
|
||||||
|
|
||||||
DescriptorSetManager = new DescriptorSetManager(_device);
|
DescriptorSetManager = new DescriptorSetManager(_device);
|
||||||
|
|
||||||
|
@ -244,10 +231,73 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
HelperShader = new HelperShader(this, _device);
|
HelperShader = new HelperShader(this, _device);
|
||||||
|
|
||||||
_counters = new Counters(this, _device, _pipeline);
|
_counters = new Counters(this, _device, _pipeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void SetupContext(GraphicsDebugLevel logLevel)
|
||||||
|
{
|
||||||
|
var api = Vk.GetApi();
|
||||||
|
|
||||||
|
Api = api;
|
||||||
|
|
||||||
|
_instance = VulkanInitialization.CreateInstance(api, logLevel, GetRequiredExtensions(), out ExtDebugReport debugReport, out _debugReportCallback);
|
||||||
|
|
||||||
|
DebugReportApi = debugReport;
|
||||||
|
|
||||||
|
if (api.TryGetInstanceExtension(_instance, out KhrSurface surfaceApi))
|
||||||
|
{
|
||||||
|
SurfaceApi = surfaceApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
_surface = GetSurface(_instance, api);
|
||||||
|
_physicalDevice = VulkanInitialization.FindSuitablePhysicalDevice(api, _instance, _surface);
|
||||||
|
|
||||||
|
var queueFamilyIndex = VulkanInitialization.FindSuitableQueueFamily(api, _physicalDevice, _surface, out uint maxQueueCount);
|
||||||
|
var supportedExtensions = VulkanInitialization.GetSupportedExtensions(api, _physicalDevice);
|
||||||
|
|
||||||
|
_device = VulkanInitialization.CreateDevice(api, _physicalDevice, queueFamilyIndex, supportedExtensions, maxQueueCount);
|
||||||
|
|
||||||
|
if (api.TryGetDeviceExtension(_instance, _device, out KhrSwapchain swapchainApi))
|
||||||
|
{
|
||||||
|
SwapchainApi = swapchainApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
api.GetDeviceQueue(_device, queueFamilyIndex, 0, out var queue);
|
||||||
|
Queue = queue;
|
||||||
|
QueueLock = new object();
|
||||||
|
|
||||||
|
LoadFeatures(supportedExtensions, maxQueueCount, queueFamilyIndex);
|
||||||
|
|
||||||
_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 IShader CompileShader(ShaderStage stage, ShaderBindings bindings, string code)
|
public IShader CompileShader(ShaderStage stage, ShaderBindings bindings, string code)
|
||||||
{
|
{
|
||||||
return new Shader(Api, _device, stage, bindings, code);
|
return new Shader(Api, _device, stage, bindings, code);
|
||||||
|
@ -465,7 +515,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
public void Initialize(GraphicsDebugLevel logLevel)
|
public void Initialize(GraphicsDebugLevel logLevel)
|
||||||
{
|
{
|
||||||
SetupContext(logLevel);
|
if (IsOffScreen)
|
||||||
|
{
|
||||||
|
SetupOffScreenContext(logLevel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetupContext(logLevel);
|
||||||
|
}
|
||||||
|
|
||||||
PrintGpuInformation();
|
PrintGpuInformation();
|
||||||
}
|
}
|
||||||
|
@ -507,8 +564,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
DescriptorSetManager.Dispose();
|
DescriptorSetManager.Dispose();
|
||||||
PipelineLayoutCache.Dispose();
|
PipelineLayoutCache.Dispose();
|
||||||
|
|
||||||
SurfaceApi.DestroySurface(_instance, _surface, null);
|
|
||||||
|
|
||||||
MemoryAllocator.Dispose();
|
MemoryAllocator.Dispose();
|
||||||
|
|
||||||
if (_debugReportCallback.Handle != 0)
|
if (_debugReportCallback.Handle != 0)
|
||||||
|
@ -531,10 +586,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
sampler.Dispose();
|
sampler.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Api.DestroyDevice(_device, null);
|
if (!IsOffScreen)
|
||||||
|
{
|
||||||
|
SurfaceApi.DestroySurface(_instance, _surface, null);
|
||||||
|
|
||||||
// Last step destroy the instance
|
Api.DestroyDevice(_device, null);
|
||||||
Api.DestroyInstance(_instance, null);
|
|
||||||
|
// Last step destroy the instance
|
||||||
|
Api.DestroyInstance(_instance, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BackgroundContextAction(Action action, bool alwaysBackground = false)
|
public void BackgroundContextAction(Action action, bool alwaysBackground = false)
|
||||||
|
|
|
@ -10,20 +10,13 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
unsafe static class VulkanInitialization
|
public unsafe static class VulkanInitialization
|
||||||
{
|
{
|
||||||
private const uint InvalidIndex = uint.MaxValue;
|
private const uint InvalidIndex = uint.MaxValue;
|
||||||
private const string AppName = "Ryujinx.Graphics.Vulkan";
|
private const string AppName = "Ryujinx.Graphics.Vulkan";
|
||||||
private const int QueuesCount = 2;
|
private const int QueuesCount = 2;
|
||||||
|
|
||||||
private static readonly string[] _requiredExtensions = new string[]
|
public static string[] DesirableExtensions { get; } = new string[]
|
||||||
{
|
|
||||||
KhrSwapchain.ExtensionName,
|
|
||||||
"VK_EXT_shader_subgroup_vote",
|
|
||||||
ExtTransformFeedback.ExtensionName
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly string[] _desirableExtensions = new string[]
|
|
||||||
{
|
{
|
||||||
ExtConditionalRendering.ExtensionName,
|
ExtConditionalRendering.ExtensionName,
|
||||||
ExtExtendedDynamicState.ExtensionName,
|
ExtExtendedDynamicState.ExtensionName,
|
||||||
|
@ -37,7 +30,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
"VK_NV_geometry_shader_passthrough"
|
"VK_NV_geometry_shader_passthrough"
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly string[] _excludedMessages = new string[]
|
public static string[] RequiredExtensions { get; } = new string[]
|
||||||
|
{
|
||||||
|
KhrSwapchain.ExtensionName,
|
||||||
|
"VK_EXT_shader_subgroup_vote",
|
||||||
|
ExtTransformFeedback.ExtensionName
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string[] _excludedMessages = new string[]
|
||||||
{
|
{
|
||||||
// NOTE: Done on purpuse right now.
|
// NOTE: Done on purpuse right now.
|
||||||
"UNASSIGNED-CoreValidation-Shader-OutputNotConsumed",
|
"UNASSIGNED-CoreValidation-Shader-OutputNotConsumed",
|
||||||
|
@ -49,7 +49,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
"VUID-VkSubpassDependency-srcSubpass-00867"
|
"VUID-VkSubpassDependency-srcSubpass-00867"
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugReport debugReport, out DebugReportCallbackEXT debugReportCallback)
|
internal static Instance CreateInstance(Vk api, GraphicsDebugLevel logLevel, string[] requiredExtensions, out ExtDebugReport debugReport, out DebugReportCallbackEXT debugReportCallback)
|
||||||
{
|
{
|
||||||
var enabledLayers = new List<string>();
|
var enabledLayers = new List<string>();
|
||||||
|
|
||||||
|
@ -135,38 +135,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
Marshal.FreeHGlobal(ppEnabledLayers[i]);
|
Marshal.FreeHGlobal(ppEnabledLayers[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!api.TryGetInstanceExtension(instance, out debugReport))
|
CreateDebugCallbacks(api, logLevel, instance, out debugReport, out debugReportCallback);
|
||||||
{
|
|
||||||
throw new Exception();
|
|
||||||
// TODO: Exception.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logLevel != GraphicsDebugLevel.None)
|
|
||||||
{
|
|
||||||
var flags = logLevel switch
|
|
||||||
{
|
|
||||||
GraphicsDebugLevel.Error => DebugReportFlagsEXT.DebugReportErrorBitExt,
|
|
||||||
GraphicsDebugLevel.Slowdowns => DebugReportFlagsEXT.DebugReportErrorBitExt | DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt,
|
|
||||||
GraphicsDebugLevel.All => DebugReportFlagsEXT.DebugReportInformationBitExt |
|
|
||||||
DebugReportFlagsEXT.DebugReportWarningBitExt |
|
|
||||||
DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt |
|
|
||||||
DebugReportFlagsEXT.DebugReportErrorBitExt |
|
|
||||||
DebugReportFlagsEXT.DebugReportDebugBitExt,
|
|
||||||
_ => throw new NotSupportedException()
|
|
||||||
};
|
|
||||||
var debugReportCallbackCreateInfo = new DebugReportCallbackCreateInfoEXT()
|
|
||||||
{
|
|
||||||
SType = StructureType.DebugReportCallbackCreateInfoExt,
|
|
||||||
Flags = flags,
|
|
||||||
PfnCallback = new PfnDebugReportCallbackEXT(DebugReport)
|
|
||||||
};
|
|
||||||
|
|
||||||
debugReport.CreateDebugReportCallback(instance, in debugReportCallbackCreateInfo, null, out debugReportCallback).ThrowOnError();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
debugReportCallback = default;
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
@ -218,7 +187,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PhysicalDevice FindSuitablePhysicalDevice(Vk api, Instance instance, SurfaceKHR surface)
|
internal static PhysicalDevice FindSuitablePhysicalDevice(Vk api, Instance instance, SurfaceKHR surface)
|
||||||
{
|
{
|
||||||
uint physicalDeviceCount;
|
uint physicalDeviceCount;
|
||||||
|
|
||||||
|
@ -264,17 +233,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
string extensionName = Marshal.PtrToStringAnsi((IntPtr)pExtensionProperties[i].ExtensionName);
|
string extensionName = Marshal.PtrToStringAnsi((IntPtr)pExtensionProperties[i].ExtensionName);
|
||||||
|
|
||||||
if (_requiredExtensions.Contains(extensionName))
|
if (RequiredExtensions.Contains(extensionName))
|
||||||
{
|
{
|
||||||
extensionMatches++;
|
extensionMatches++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return extensionMatches == _requiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
|
return extensionMatches == RequiredExtensions.Length && FindSuitableQueueFamily(api, physicalDevice, surface, out _) != InvalidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
|
internal static uint FindSuitableQueueFamily(Vk api, PhysicalDevice physicalDevice, SurfaceKHR surface, out uint queueCount)
|
||||||
{
|
{
|
||||||
const QueueFlags RequiredFlags = QueueFlags.QueueGraphicsBit | QueueFlags.QueueComputeBit;
|
const QueueFlags RequiredFlags = QueueFlags.QueueGraphicsBit | QueueFlags.QueueComputeBit;
|
||||||
|
|
||||||
|
@ -443,7 +412,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
pExtendedFeatures = &featuresSubgroupSizeControl;
|
pExtendedFeatures = &featuresSubgroupSizeControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
var enabledExtensions = _requiredExtensions.Union(_desirableExtensions.Intersect(supportedExtensions)).ToArray();
|
var enabledExtensions = RequiredExtensions.Union(DesirableExtensions.Intersect(supportedExtensions)).ToArray();
|
||||||
|
|
||||||
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
|
IntPtr* ppEnabledExtensions = stackalloc IntPtr[enabledExtensions.Length];
|
||||||
|
|
||||||
|
@ -489,9 +458,47 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToArray();
|
return extensionProperties.Select(x => Marshal.PtrToStringAnsi((IntPtr)x.ExtensionName)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CommandBufferPool CreateCommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex)
|
internal static CommandBufferPool CreateCommandBufferPool(Vk api, Device device, Queue queue, object queueLock, uint queueFamilyIndex)
|
||||||
{
|
{
|
||||||
return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
|
return new CommandBufferPool(api, device, queue, queueLock, queueFamilyIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal unsafe static void CreateDebugCallbacks(Vk api, GraphicsDebugLevel logLevel, Instance instance, out ExtDebugReport debugReport, out DebugReportCallbackEXT debugReportCallback)
|
||||||
|
{
|
||||||
|
debugReport = default;
|
||||||
|
|
||||||
|
if (logLevel != GraphicsDebugLevel.None)
|
||||||
|
{
|
||||||
|
if (!api.TryGetInstanceExtension(instance, out debugReport))
|
||||||
|
{
|
||||||
|
throw new Exception();
|
||||||
|
// TODO: Exception.
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags = logLevel switch
|
||||||
|
{
|
||||||
|
GraphicsDebugLevel.Error => DebugReportFlagsEXT.DebugReportErrorBitExt,
|
||||||
|
GraphicsDebugLevel.Slowdowns => DebugReportFlagsEXT.DebugReportErrorBitExt | DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt,
|
||||||
|
GraphicsDebugLevel.All => DebugReportFlagsEXT.DebugReportInformationBitExt |
|
||||||
|
DebugReportFlagsEXT.DebugReportWarningBitExt |
|
||||||
|
DebugReportFlagsEXT.DebugReportPerformanceWarningBitExt |
|
||||||
|
DebugReportFlagsEXT.DebugReportErrorBitExt |
|
||||||
|
DebugReportFlagsEXT.DebugReportDebugBitExt,
|
||||||
|
_ => throw new NotSupportedException()
|
||||||
|
};
|
||||||
|
var debugReportCallbackCreateInfo = new DebugReportCallbackCreateInfoEXT()
|
||||||
|
{
|
||||||
|
SType = StructureType.DebugReportCallbackCreateInfoExt,
|
||||||
|
Flags = flags,
|
||||||
|
PfnCallback = new PfnDebugReportCallbackEXT(DebugReport)
|
||||||
|
};
|
||||||
|
|
||||||
|
debugReport.CreateDebugReportCallback(instance, in debugReportCallbackCreateInfo, null, out debugReportCallback).ThrowOnError();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
debugReportCallback = default;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ using VkFormat = Silk.NET.Vulkan.Format;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
class Window : IWindow, IDisposable
|
class Window : WindowBase, IDisposable
|
||||||
{
|
{
|
||||||
private const int SurfaceWidth = 1280;
|
private const int SurfaceWidth = 1280;
|
||||||
private const int SurfaceHeight = 720;
|
private const int SurfaceHeight = 720;
|
||||||
|
@ -27,8 +27,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private int _height;
|
private int _height;
|
||||||
private VkFormat _format;
|
private VkFormat _format;
|
||||||
|
|
||||||
internal bool ScreenCaptureRequested { get; set; }
|
|
||||||
|
|
||||||
public unsafe Window(VulkanGraphicsDevice gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
|
public unsafe Window(VulkanGraphicsDevice gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
|
||||||
{
|
{
|
||||||
_gd = gd;
|
_gd = gd;
|
||||||
|
@ -211,7 +209,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback)
|
public unsafe override void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback)
|
||||||
{
|
{
|
||||||
uint nextImage = 0;
|
uint nextImage = 0;
|
||||||
|
|
||||||
|
@ -401,7 +399,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_gd.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY));
|
_gd.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetSize(int width, int height)
|
public override void SetSize(int width, int height)
|
||||||
{
|
{
|
||||||
// Not needed as we can get the size from the surface.
|
// Not needed as we can get the size from the surface.
|
||||||
}
|
}
|
||||||
|
@ -426,7 +424,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
}
|
}
|
||||||
|
|
14
Ryujinx.Graphics.Vulkan/WindowBase.cs
Normal file
14
Ryujinx.Graphics.Vulkan/WindowBase.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
|
{
|
||||||
|
internal abstract class WindowBase: IWindow
|
||||||
|
{
|
||||||
|
public bool ScreenCaptureRequested { get; set; }
|
||||||
|
|
||||||
|
public abstract void Dispose();
|
||||||
|
public abstract void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback);
|
||||||
|
public abstract void SetSize(int width, int height);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue