Avalonia - Couple fixes and improvements to vulkan (#3483)
* drop split devices, rebase * add fallback to opengl if vulkan is not available * addressed review * ensure present image references are incremented and decremented when necessary * allow changing vsync for vulkan * fix screenshot on avalonia vulkan * save favorite when toggled * improve sync between popups * use separate devices for each new window * fix crash when closing window * addressed review * don't create the main window with immediate mode * change skia vk delegate to method * update vulkan throwonerror * addressed review
This commit is contained in:
parent
0ec933a615
commit
c8f9292bab
28 changed files with 585 additions and 312 deletions
|
@ -12,6 +12,7 @@ using Ryujinx.Audio.Integration;
|
||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Input;
|
using Ryujinx.Ava.Input;
|
||||||
|
using Ryujinx.Ava.Ui.Backend.Vulkan;
|
||||||
using Ryujinx.Ava.Ui.Controls;
|
using Ryujinx.Ava.Ui.Controls;
|
||||||
using Ryujinx.Ava.Ui.Models;
|
using Ryujinx.Ava.Ui.Models;
|
||||||
using Ryujinx.Ava.Ui.Vulkan;
|
using Ryujinx.Ava.Ui.Vulkan;
|
||||||
|
@ -334,6 +335,8 @@ namespace Ryujinx.Ava
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AvaloniaLocator.Current.GetService<VulkanPlatformInterface>()?.MainSurface.Display.ChangeVSyncMode(true);
|
||||||
|
|
||||||
_isStopped = true;
|
_isStopped = true;
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
}
|
}
|
||||||
|
@ -596,12 +599,13 @@ namespace Ryujinx.Ava
|
||||||
if (Program.UseVulkan)
|
if (Program.UseVulkan)
|
||||||
{
|
{
|
||||||
var vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
var vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
|
|
||||||
renderer = new VulkanRenderer(vulkan.Instance.InternalHandle,
|
renderer = new VulkanRenderer(vulkan.Instance.InternalHandle,
|
||||||
vulkan.Device.InternalHandle,
|
vulkan.MainSurface.Device.InternalHandle,
|
||||||
vulkan.PhysicalDevice.InternalHandle,
|
vulkan.PhysicalDevice.InternalHandle,
|
||||||
vulkan.Device.Queue.InternalHandle,
|
vulkan.MainSurface.Device.Queue.InternalHandle,
|
||||||
vulkan.PhysicalDevice.QueueFamilyIndex,
|
vulkan.PhysicalDevice.QueueFamilyIndex,
|
||||||
vulkan.Device.Lock);
|
vulkan.MainSurface.Device.Lock);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -775,7 +779,10 @@ namespace Ryujinx.Ava
|
||||||
Width = (int)e.Width;
|
Width = (int)e.Width;
|
||||||
Height = (int)e.Height;
|
Height = (int)e.Height;
|
||||||
|
|
||||||
SetRendererWindowSize(e);
|
if (!Program.UseVulkan)
|
||||||
|
{
|
||||||
|
SetRendererWindowSize(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MainLoop()
|
private void MainLoop()
|
||||||
|
@ -815,12 +822,11 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
||||||
|
|
||||||
if (!Program.UseVulkan)
|
(_renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext((Renderer as OpenGLRendererControl).GameContext));
|
||||||
{
|
|
||||||
(_renderer as OpenGLRenderer).InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext((Renderer as OpenGLRendererControl).GameContext));
|
|
||||||
|
|
||||||
Renderer.MakeCurrent();
|
Renderer.MakeCurrent();
|
||||||
}
|
|
||||||
|
AvaloniaLocator.Current.GetService<VulkanPlatformInterface>()?.MainSurface?.Display?.ChangeVSyncMode(Device.EnableDeviceVsync);
|
||||||
|
|
||||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||||
|
|
||||||
|
@ -837,8 +843,6 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
Renderer.Start();
|
Renderer.Start();
|
||||||
|
|
||||||
Renderer.QueueRender();
|
|
||||||
|
|
||||||
while (_isActive)
|
while (_isActive)
|
||||||
{
|
{
|
||||||
if (Device.WaitFifo())
|
if (Device.WaitFifo())
|
||||||
|
@ -889,6 +893,16 @@ namespace Ryujinx.Ava
|
||||||
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
|
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
|
||||||
$"GPU: {_renderer.GetHardwareInfo().GpuVendor}"));
|
$"GPU: {_renderer.GetHardwareInfo().GpuVendor}"));
|
||||||
|
|
||||||
|
if (Program.UseVulkan)
|
||||||
|
{
|
||||||
|
var platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
|
if (platformInterface.MainSurface.Display.IsSurfaceChanged())
|
||||||
|
{
|
||||||
|
SetRendererWindowSize(new Size(Width, Height));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Renderer.Present(image);
|
Renderer.Present(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -970,6 +984,9 @@ namespace Ryujinx.Ava
|
||||||
{
|
{
|
||||||
case KeyboardHotkeyState.ToggleVSync:
|
case KeyboardHotkeyState.ToggleVSync:
|
||||||
Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
|
Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
|
||||||
|
|
||||||
|
AvaloniaLocator.Current.GetService<VulkanPlatformInterface>()?.MainSurface?.Display?.ChangeVSyncMode(Device.EnableDeviceVsync);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case KeyboardHotkeyState.Screenshot:
|
case KeyboardHotkeyState.Screenshot:
|
||||||
ScreenshotRequested = true;
|
ScreenshotRequested = true;
|
||||||
|
|
|
@ -94,7 +94,6 @@ namespace Ryujinx.Ava
|
||||||
.With(new Ui.Vulkan.VulkanOptions()
|
.With(new Ui.Vulkan.VulkanOptions()
|
||||||
{
|
{
|
||||||
ApplicationName = "Ryujinx.Graphics.Vulkan",
|
ApplicationName = "Ryujinx.Graphics.Vulkan",
|
||||||
VulkanVersion = new Version(1, 2),
|
|
||||||
MaxQueueCount = 2,
|
MaxQueueCount = 2,
|
||||||
PreferDiscreteGpu = true,
|
PreferDiscreteGpu = true,
|
||||||
PreferredDevice = !PreviewerDetached ? "" : ConfigurationState.Instance.Graphics.PreferredGpu.Value,
|
PreferredDevice = !PreviewerDetached ? "" : ConfigurationState.Instance.Graphics.PreferredGpu.Value,
|
||||||
|
@ -181,6 +180,18 @@ namespace Ryujinx.Ava
|
||||||
|
|
||||||
UseVulkan = PreviewerDetached ? ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan : false;
|
UseVulkan = PreviewerDetached ? ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan : false;
|
||||||
|
|
||||||
|
if (UseVulkan)
|
||||||
|
{
|
||||||
|
if (VulkanRenderer.GetPhysicalDevices().Length == 0)
|
||||||
|
{
|
||||||
|
UseVulkan = false;
|
||||||
|
|
||||||
|
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
|
||||||
|
|
||||||
|
Logger.Warning?.PrintMsg(LogClass.Application, "A suitable Vulkan physical device is not available. Falling back to OpenGL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (UseVulkan)
|
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,
|
// 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,
|
||||||
|
|
|
@ -7,7 +7,8 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
{
|
{
|
||||||
public static void ThrowOnError(this Result result)
|
public static void ThrowOnError(this Result result)
|
||||||
{
|
{
|
||||||
if (result != Result.Success)
|
// Only negative result codes are errors.
|
||||||
|
if ((int)result < (int)Result.Success)
|
||||||
{
|
{
|
||||||
throw new Exception($"Unexpected API error \"{result}\".");
|
throw new Exception($"Unexpected API error \"{result}\".");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,90 @@
|
||||||
using System;
|
using System;
|
||||||
|
using Avalonia;
|
||||||
using Avalonia.Skia;
|
using Avalonia.Skia;
|
||||||
using Ryujinx.Ava.Ui.Vulkan;
|
using Ryujinx.Ava.Ui.Vulkan;
|
||||||
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Backend.Vulkan
|
namespace Ryujinx.Ava.Ui.Backend.Vulkan
|
||||||
{
|
{
|
||||||
internal class VulkanRenderTarget : ISkiaGpuRenderTarget
|
internal class VulkanRenderTarget : ISkiaGpuRenderTarget
|
||||||
{
|
{
|
||||||
public GRContext GrContext { get; set; }
|
public GRContext GrContext { get; private set; }
|
||||||
|
|
||||||
private readonly VulkanSurfaceRenderTarget _surface;
|
private readonly VulkanSurfaceRenderTarget _surface;
|
||||||
|
private readonly VulkanPlatformInterface _vulkanPlatformInterface;
|
||||||
private readonly IVulkanPlatformSurface _vulkanPlatformSurface;
|
private readonly IVulkanPlatformSurface _vulkanPlatformSurface;
|
||||||
|
private GRVkBackendContext _grVkBackend;
|
||||||
|
|
||||||
public VulkanRenderTarget(VulkanPlatformInterface vulkanPlatformInterface, IVulkanPlatformSurface vulkanPlatformSurface)
|
public VulkanRenderTarget(VulkanPlatformInterface vulkanPlatformInterface, IVulkanPlatformSurface vulkanPlatformSurface)
|
||||||
{
|
{
|
||||||
_surface = vulkanPlatformInterface.CreateRenderTarget(vulkanPlatformSurface);
|
_surface = vulkanPlatformInterface.CreateRenderTarget(vulkanPlatformSurface);
|
||||||
|
_vulkanPlatformInterface = vulkanPlatformInterface;
|
||||||
_vulkanPlatformSurface = vulkanPlatformSurface;
|
_vulkanPlatformSurface = vulkanPlatformSurface;
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
GRVkGetProcedureAddressDelegate getProc = GetVulkanProcAddress;
|
||||||
|
|
||||||
|
_grVkBackend = new GRVkBackendContext()
|
||||||
|
{
|
||||||
|
VkInstance = _surface.Device.Handle,
|
||||||
|
VkPhysicalDevice = _vulkanPlatformInterface.PhysicalDevice.Handle,
|
||||||
|
VkDevice = _surface.Device.Handle,
|
||||||
|
VkQueue = _surface.Device.Queue.Handle,
|
||||||
|
GraphicsQueueIndex = _vulkanPlatformInterface.PhysicalDevice.QueueFamilyIndex,
|
||||||
|
GetProcedureAddress = getProc
|
||||||
|
};
|
||||||
|
|
||||||
|
GrContext = GRContext.CreateVulkan(_grVkBackend);
|
||||||
|
|
||||||
|
var gpu = AvaloniaLocator.Current.GetService<VulkanSkiaGpu>();
|
||||||
|
|
||||||
|
if (gpu.MaxResourceBytes.HasValue)
|
||||||
|
{
|
||||||
|
GrContext.SetResourceCacheLimit(gpu.MaxResourceBytes.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntPtr GetVulkanProcAddress(string name, IntPtr instanceHandle, IntPtr deviceHandle)
|
||||||
|
{
|
||||||
|
IntPtr addr;
|
||||||
|
|
||||||
|
if (deviceHandle != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
addr = _vulkanPlatformInterface.Api.GetDeviceProcAddr(new Device(deviceHandle), name);
|
||||||
|
|
||||||
|
if (addr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = _vulkanPlatformInterface.Api.GetDeviceProcAddr(new Device(_surface.Device.Handle), name);
|
||||||
|
|
||||||
|
if (addr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = _vulkanPlatformInterface.Api.GetInstanceProcAddr(new Instance(_vulkanPlatformInterface.Instance.Handle), name);
|
||||||
|
|
||||||
|
if (addr == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
addr = _vulkanPlatformInterface.Api.GetInstanceProcAddr(new Instance(instanceHandle), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_grVkBackend.Dispose();
|
||||||
|
GrContext.Dispose();
|
||||||
_surface.Dispose();
|
_surface.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,20 +109,22 @@ namespace Ryujinx.Ava.Ui.Backend.Vulkan
|
||||||
{
|
{
|
||||||
GrContext.ResetContext();
|
GrContext.ResetContext();
|
||||||
|
|
||||||
|
var image = _surface.GetImage();
|
||||||
|
|
||||||
var imageInfo = new GRVkImageInfo()
|
var imageInfo = new GRVkImageInfo()
|
||||||
{
|
{
|
||||||
CurrentQueueFamily = disp.QueueFamilyIndex,
|
CurrentQueueFamily = disp.QueueFamilyIndex,
|
||||||
Format = _surface.ImageFormat,
|
Format = (uint)image.Format,
|
||||||
Image = _surface.Image.Handle,
|
Image = image.Handle,
|
||||||
ImageLayout = (uint)_surface.Image.CurrentLayout,
|
ImageLayout = (uint)image.CurrentLayout,
|
||||||
ImageTiling = (uint)_surface.Image.Tiling,
|
ImageTiling = (uint)image.Tiling,
|
||||||
ImageUsageFlags = _surface.UsageFlags,
|
ImageUsageFlags = _surface.UsageFlags,
|
||||||
LevelCount = _surface.MipLevels,
|
LevelCount = _surface.MipLevels,
|
||||||
SampleCount = 1,
|
SampleCount = 1,
|
||||||
Protected = false,
|
Protected = false,
|
||||||
Alloc = new GRVkAlloc()
|
Alloc = new GRVkAlloc()
|
||||||
{
|
{
|
||||||
Memory = _surface.Image.MemoryHandle,
|
Memory = image.MemoryHandle,
|
||||||
Flags = 0,
|
Flags = 0,
|
||||||
Offset = 0,
|
Offset = 0,
|
||||||
Size = _surface.MemorySize
|
Size = _surface.MemorySize
|
||||||
|
|
|
@ -13,71 +13,12 @@ namespace Ryujinx.Ava.Ui.Backend.Vulkan
|
||||||
public class VulkanSkiaGpu : ISkiaGpu
|
public class VulkanSkiaGpu : ISkiaGpu
|
||||||
{
|
{
|
||||||
private readonly VulkanPlatformInterface _vulkan;
|
private readonly VulkanPlatformInterface _vulkan;
|
||||||
private readonly long? _maxResourceBytes;
|
public long? MaxResourceBytes { get; }
|
||||||
private GRVkBackendContext _grVkBackend;
|
|
||||||
private bool _initialized;
|
|
||||||
|
|
||||||
public GRContext GrContext { get; private set; }
|
|
||||||
|
|
||||||
public VulkanSkiaGpu(long? maxResourceBytes)
|
public VulkanSkiaGpu(long? maxResourceBytes)
|
||||||
{
|
{
|
||||||
_vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
_vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
_maxResourceBytes = maxResourceBytes;
|
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)
|
public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces)
|
||||||
|
@ -106,10 +47,6 @@ namespace Ryujinx.Ava.Ui.Backend.Vulkan
|
||||||
|
|
||||||
VulkanRenderTarget vulkanRenderTarget = new VulkanRenderTarget(_vulkan, window);
|
VulkanRenderTarget vulkanRenderTarget = new VulkanRenderTarget(_vulkan, window);
|
||||||
|
|
||||||
Initialize();
|
|
||||||
|
|
||||||
vulkanRenderTarget.GrContext = GrContext;
|
|
||||||
|
|
||||||
return vulkanRenderTarget;
|
return vulkanRenderTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
|
namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
|
||||||
|
@ -7,24 +8,35 @@ namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
|
||||||
internal class VulkanSurfaceRenderTarget : IDisposable
|
internal class VulkanSurfaceRenderTarget : IDisposable
|
||||||
{
|
{
|
||||||
private readonly VulkanPlatformInterface _platformInterface;
|
private readonly VulkanPlatformInterface _platformInterface;
|
||||||
|
|
||||||
private readonly Format _format;
|
private readonly Format _format;
|
||||||
|
|
||||||
public VulkanImage Image { get; private set; }
|
private VulkanCommandBufferPool.VulkanCommandBuffer _commandBuffer;
|
||||||
public bool IsCorrupted { get; private set; } = true;
|
private VulkanImage Image { get; set; }
|
||||||
|
private object _lock = new object();
|
||||||
|
|
||||||
public uint MipLevels => Image.MipLevels;
|
public uint MipLevels => Image.MipLevels;
|
||||||
|
public VulkanDevice Device { get; }
|
||||||
|
|
||||||
public VulkanSurfaceRenderTarget(VulkanPlatformInterface platformInterface, VulkanSurface surface)
|
public VulkanSurfaceRenderTarget(VulkanPlatformInterface platformInterface, VulkanSurface surface)
|
||||||
{
|
{
|
||||||
_platformInterface = platformInterface;
|
_platformInterface = platformInterface;
|
||||||
|
|
||||||
Display = VulkanDisplay.CreateDisplay(platformInterface.Instance, platformInterface.Device,
|
var device = VulkanInitialization.CreateDevice(platformInterface.Api,
|
||||||
platformInterface.PhysicalDevice, surface);
|
platformInterface.PhysicalDevice.InternalHandle,
|
||||||
|
platformInterface.PhysicalDevice.QueueFamilyIndex,
|
||||||
|
VulkanInitialization.GetSupportedExtensions(platformInterface.Api, platformInterface.PhysicalDevice.InternalHandle),
|
||||||
|
platformInterface.PhysicalDevice.QueueCount);
|
||||||
|
|
||||||
|
Device = new VulkanDevice(device, platformInterface.PhysicalDevice, platformInterface.Api);
|
||||||
|
|
||||||
|
Display = VulkanDisplay.CreateDisplay(
|
||||||
|
platformInterface.Instance,
|
||||||
|
Device,
|
||||||
|
platformInterface.PhysicalDevice,
|
||||||
|
surface);
|
||||||
Surface = surface;
|
Surface = surface;
|
||||||
|
|
||||||
// Skia seems to only create surfaces from images with unorm format
|
// Skia seems to only create surfaces from images with unorm format
|
||||||
|
|
||||||
IsRgba = Display.SurfaceFormat.Format >= Format.R8G8B8A8Unorm &&
|
IsRgba = Display.SurfaceFormat.Format >= Format.R8G8B8A8Unorm &&
|
||||||
Display.SurfaceFormat.Format <= Format.R8G8B8A8Srgb;
|
Display.SurfaceFormat.Format <= Format.R8G8B8A8Srgb;
|
||||||
|
|
||||||
|
@ -33,13 +45,13 @@ namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
|
||||||
|
|
||||||
public bool IsRgba { get; }
|
public bool IsRgba { get; }
|
||||||
|
|
||||||
public uint ImageFormat => (uint) _format;
|
public uint ImageFormat => (uint)_format;
|
||||||
|
|
||||||
public ulong MemorySize => Image.MemorySize;
|
public ulong MemorySize => Image.MemorySize;
|
||||||
|
|
||||||
public VulkanDisplay Display { get; }
|
public VulkanDisplay Display { get; private set; }
|
||||||
|
|
||||||
public VulkanSurface Surface { get; }
|
public VulkanSurface Surface { get; private set; }
|
||||||
|
|
||||||
public uint UsageFlags => Image.UsageFlags;
|
public uint UsageFlags => Image.UsageFlags;
|
||||||
|
|
||||||
|
@ -47,46 +59,76 @@ namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_platformInterface.Device.WaitIdle();
|
lock (_lock)
|
||||||
DestroyImage();
|
{
|
||||||
Display?.Dispose();
|
DestroyImage();
|
||||||
Surface?.Dispose();
|
Display?.Dispose();
|
||||||
|
Surface?.Dispose();
|
||||||
|
Device?.Dispose();
|
||||||
|
|
||||||
|
Display = null;
|
||||||
|
Surface = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public VulkanSurfaceRenderingSession BeginDraw(float scaling)
|
public VulkanSurfaceRenderingSession BeginDraw(float scaling)
|
||||||
{
|
{
|
||||||
var session = new VulkanSurfaceRenderingSession(Display, _platformInterface.Device, this, scaling);
|
if (Image == null)
|
||||||
|
{
|
||||||
|
RecreateImage();
|
||||||
|
}
|
||||||
|
|
||||||
if (IsCorrupted)
|
_commandBuffer?.WaitForFence();
|
||||||
{
|
_commandBuffer = null;
|
||||||
IsCorrupted = false;
|
|
||||||
DestroyImage();
|
var session = new VulkanSurfaceRenderingSession(Display, Device, this, scaling);
|
||||||
CreateImage();
|
|
||||||
}
|
Image.TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr);
|
||||||
else
|
|
||||||
{
|
|
||||||
Image.TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Invalidate()
|
public void RecreateImage()
|
||||||
{
|
{
|
||||||
IsCorrupted = true;
|
DestroyImage();
|
||||||
|
CreateImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateImage()
|
private void CreateImage()
|
||||||
{
|
{
|
||||||
Size = Display.Size;
|
Size = Display.Size;
|
||||||
|
|
||||||
Image = new VulkanImage(_platformInterface.Device, _platformInterface.PhysicalDevice, _platformInterface.Device.CommandBufferPool, ImageFormat, Size);
|
Image = new VulkanImage(Device, _platformInterface.PhysicalDevice, Display.CommandBufferPool, ImageFormat, Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DestroyImage()
|
private void DestroyImage()
|
||||||
{
|
{
|
||||||
_platformInterface.Device.WaitIdle();
|
_commandBuffer?.WaitForFence();
|
||||||
|
_commandBuffer = null;
|
||||||
Image?.Dispose();
|
Image?.Dispose();
|
||||||
|
Image = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VulkanImage GetImage()
|
||||||
|
{
|
||||||
|
return Image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndDraw()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (Display == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_commandBuffer = Display.StartPresentation();
|
||||||
|
|
||||||
|
Display.BlitImageToCurrentImage(this, _commandBuffer.InternalHandle);
|
||||||
|
|
||||||
|
Display.EndPresentation(_commandBuffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
private readonly CommandPool _commandPool;
|
private readonly CommandPool _commandPool;
|
||||||
|
|
||||||
private readonly List<VulkanCommandBuffer> _usedCommandBuffers = new();
|
private readonly List<VulkanCommandBuffer> _usedCommandBuffers = new();
|
||||||
|
private readonly object _lock = new object();
|
||||||
|
|
||||||
public unsafe VulkanCommandBufferPool(VulkanDevice device, VulkanPhysicalDevice physicalDevice)
|
public unsafe VulkanCommandBufferPool(VulkanDevice device, VulkanPhysicalDevice physicalDevice)
|
||||||
{
|
{
|
||||||
|
@ -36,9 +37,12 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
Level = CommandBufferLevel.Primary
|
Level = CommandBufferLevel.Primary
|
||||||
};
|
};
|
||||||
|
|
||||||
_device.Api.AllocateCommandBuffers(_device.InternalHandle, commandBufferAllocateInfo, out var commandBuffer);
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_device.Api.AllocateCommandBuffers(_device.InternalHandle, commandBufferAllocateInfo, out var commandBuffer);
|
||||||
|
|
||||||
return commandBuffer;
|
return commandBuffer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public VulkanCommandBuffer CreateCommandBuffer()
|
public VulkanCommandBuffer CreateCommandBuffer()
|
||||||
|
@ -48,7 +52,7 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
|
||||||
public void FreeUsedCommandBuffers()
|
public void FreeUsedCommandBuffers()
|
||||||
{
|
{
|
||||||
lock (_usedCommandBuffers)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
foreach (var usedCommandBuffer in _usedCommandBuffers)
|
foreach (var usedCommandBuffer in _usedCommandBuffers)
|
||||||
{
|
{
|
||||||
|
@ -61,7 +65,7 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
|
||||||
private void DisposeCommandBuffer(VulkanCommandBuffer commandBuffer)
|
private void DisposeCommandBuffer(VulkanCommandBuffer commandBuffer)
|
||||||
{
|
{
|
||||||
lock (_usedCommandBuffers)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
_usedCommandBuffers.Add(commandBuffer);
|
_usedCommandBuffers.Add(commandBuffer);
|
||||||
}
|
}
|
||||||
|
@ -69,8 +73,11 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
FreeUsedCommandBuffers();
|
lock (_lock)
|
||||||
_device.Api.DestroyCommandPool(_device.InternalHandle, _commandPool, Span<AllocationCallbacks>.Empty);
|
{
|
||||||
|
FreeUsedCommandBuffers();
|
||||||
|
_device.Api.DestroyCommandPool(_device.InternalHandle, _commandPool, Span<AllocationCallbacks>.Empty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VulkanCommandBuffer : IDisposable
|
public class VulkanCommandBuffer : IDisposable
|
||||||
|
@ -80,6 +87,8 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
private readonly Fence _fence;
|
private readonly Fence _fence;
|
||||||
private bool _hasEnded;
|
private bool _hasEnded;
|
||||||
private bool _hasStarted;
|
private bool _hasStarted;
|
||||||
|
private bool _isDisposed;
|
||||||
|
private object _lock = new object();
|
||||||
|
|
||||||
public IntPtr Handle => InternalHandle.Handle;
|
public IntPtr Handle => InternalHandle.Handle;
|
||||||
|
|
||||||
|
@ -101,6 +110,22 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
device.Api.CreateFence(device.InternalHandle, fenceCreateInfo, null, out _fence);
|
device.Api.CreateFence(device.InternalHandle, fenceCreateInfo, null, out _fence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WaitForFence()
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
_device.Api.WaitForFences(_device.InternalHandle, 1, _fence, true, ulong.MaxValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void BeginRecording()
|
public void BeginRecording()
|
||||||
{
|
{
|
||||||
if (!_hasStarted)
|
if (!_hasStarted)
|
||||||
|
@ -173,9 +198,17 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_device.Api.WaitForFences(_device.InternalHandle, 1, _fence, true, ulong.MaxValue);
|
lock (_lock)
|
||||||
_device.Api.FreeCommandBuffers(_device.InternalHandle, _commandBufferPool._commandPool, 1, InternalHandle);
|
{
|
||||||
_device.Api.DestroyFence(_device.InternalHandle, _fence, Span<AllocationCallbacks>.Empty);
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
_isDisposed = true;
|
||||||
|
|
||||||
|
_device.Api.WaitForFences(_device.InternalHandle, 1, _fence, true, ulong.MaxValue);
|
||||||
|
_device.Api.FreeCommandBuffers(_device.InternalHandle, _commandBufferPool._commandPool, 1, InternalHandle);
|
||||||
|
_device.Api.DestroyFence(_device.InternalHandle, _fence, Span<AllocationCallbacks>.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,9 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
|
||||||
api.GetDeviceQueue(apiHandle, physicalDevice.QueueFamilyIndex, 0, out var queue);
|
api.GetDeviceQueue(apiHandle, physicalDevice.QueueFamilyIndex, 0, out var queue);
|
||||||
|
|
||||||
var vulkanQueue = new VulkanQueue(this, queue);
|
Queue = new VulkanQueue(this, queue);
|
||||||
Queue = vulkanQueue;
|
|
||||||
|
|
||||||
PresentQueue = vulkanQueue;
|
PresentQueue = Queue;
|
||||||
|
|
||||||
CommandBufferPool = new VulkanCommandBufferPool(this, physicalDevice);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntPtr Handle => InternalHandle.Handle;
|
public IntPtr Handle => InternalHandle.Handle;
|
||||||
|
@ -29,13 +26,12 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
|
||||||
public VulkanQueue Queue { get; private set; }
|
public VulkanQueue Queue { get; private set; }
|
||||||
public VulkanQueue PresentQueue { get; }
|
public VulkanQueue PresentQueue { get; }
|
||||||
public VulkanCommandBufferPool CommandBufferPool { get; }
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
WaitIdle();
|
WaitIdle();
|
||||||
CommandBufferPool?.Dispose();
|
|
||||||
Queue = null;
|
Queue = null;
|
||||||
|
Api.DestroyDevice(InternalHandle, Span<AllocationCallbacks>.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Submit(SubmitInfo submitInfo, Fence fence = default)
|
internal void Submit(SubmitInfo submitInfo, Fence fence = default)
|
||||||
|
|
|
@ -3,7 +3,6 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using Silk.NET.Vulkan.Extensions.KHR;
|
using Silk.NET.Vulkan.Extensions.KHR;
|
||||||
|
|
||||||
|
@ -15,16 +14,19 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
private readonly VulkanInstance _instance;
|
private readonly VulkanInstance _instance;
|
||||||
private readonly VulkanPhysicalDevice _physicalDevice;
|
private readonly VulkanPhysicalDevice _physicalDevice;
|
||||||
private readonly VulkanSemaphorePair _semaphorePair;
|
private readonly VulkanSemaphorePair _semaphorePair;
|
||||||
|
private readonly VulkanDevice _device;
|
||||||
private uint _nextImage;
|
private uint _nextImage;
|
||||||
private readonly VulkanSurface _surface;
|
private readonly VulkanSurface _surface;
|
||||||
private SurfaceFormatKHR _surfaceFormat;
|
private SurfaceFormatKHR _surfaceFormat;
|
||||||
private SwapchainKHR _swapchain;
|
private SwapchainKHR _swapchain;
|
||||||
private Extent2D _swapchainExtent;
|
private Extent2D _swapchainExtent;
|
||||||
private Image[] _swapchainImages;
|
private Image[] _swapchainImages;
|
||||||
private VulkanDevice _device { get; }
|
private ImageView[] _swapchainImageViews = Array.Empty<ImageView>();
|
||||||
private ImageView[] _swapchainImageViews = new ImageView[0];
|
|
||||||
private bool _vsyncStateChanged;
|
private bool _vsyncStateChanged;
|
||||||
private bool _vsyncEnabled;
|
private bool _vsyncEnabled;
|
||||||
|
private bool _surfaceChanged;
|
||||||
|
|
||||||
|
public event EventHandler Presented;
|
||||||
|
|
||||||
public VulkanCommandBufferPool CommandBufferPool { get; set; }
|
public VulkanCommandBufferPool CommandBufferPool { get; set; }
|
||||||
|
|
||||||
|
@ -73,6 +75,14 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
CommandBufferPool.Dispose();
|
CommandBufferPool.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsSurfaceChanged()
|
||||||
|
{
|
||||||
|
var changed = _surfaceChanged;
|
||||||
|
_surfaceChanged = false;
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
private static unsafe SwapchainKHR CreateSwapchain(VulkanInstance instance, VulkanDevice device,
|
private static unsafe SwapchainKHR CreateSwapchain(VulkanInstance instance, VulkanDevice device,
|
||||||
VulkanPhysicalDevice physicalDevice, VulkanSurface surface, out Extent2D swapchainExtent,
|
VulkanPhysicalDevice physicalDevice, VulkanSurface surface, out Extent2D swapchainExtent,
|
||||||
SwapchainKHR? oldswapchain = null, bool vsyncEnabled = true)
|
SwapchainKHR? oldswapchain = null, bool vsyncEnabled = true)
|
||||||
|
@ -193,22 +203,23 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
}
|
}
|
||||||
|
|
||||||
var modes = presentModes.ToList();
|
var modes = presentModes.ToList();
|
||||||
var presentMode = PresentModeKHR.PresentModeFifoKhr;
|
|
||||||
|
|
||||||
if (!vsyncEnabled && modes.Contains(PresentModeKHR.PresentModeImmediateKhr))
|
if (!vsyncEnabled && modes.Contains(PresentModeKHR.PresentModeImmediateKhr))
|
||||||
{
|
{
|
||||||
presentMode = PresentModeKHR.PresentModeImmediateKhr;
|
return PresentModeKHR.PresentModeImmediateKhr;
|
||||||
}
|
}
|
||||||
else if (modes.Contains(PresentModeKHR.PresentModeMailboxKhr))
|
else if (modes.Contains(PresentModeKHR.PresentModeMailboxKhr))
|
||||||
{
|
{
|
||||||
presentMode = PresentModeKHR.PresentModeMailboxKhr;
|
return PresentModeKHR.PresentModeMailboxKhr;
|
||||||
}
|
}
|
||||||
else if (modes.Contains(PresentModeKHR.PresentModeImmediateKhr))
|
else if (modes.Contains(PresentModeKHR.PresentModeFifoKhr))
|
||||||
{
|
{
|
||||||
presentMode = PresentModeKHR.PresentModeImmediateKhr;
|
return PresentModeKHR.PresentModeFifoKhr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return PresentModeKHR.PresentModeImmediateKhr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return presentMode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static VulkanDisplay CreateDisplay(VulkanInstance instance, VulkanDevice device,
|
internal static VulkanDisplay CreateDisplay(VulkanInstance instance, VulkanDevice device,
|
||||||
|
@ -266,6 +277,8 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
_swapchain = CreateSwapchain(_instance, _device, _physicalDevice, _surface, out _swapchainExtent, _swapchain, _vsyncEnabled);
|
_swapchain = CreateSwapchain(_instance, _device, _physicalDevice, _surface, out _swapchainExtent, _swapchain, _vsyncEnabled);
|
||||||
|
|
||||||
CreateSwapchainImages();
|
CreateSwapchainImages();
|
||||||
|
|
||||||
|
_surfaceChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe ImageView CreateSwapchainImageView(Image swapchainImage, Format format)
|
private unsafe ImageView CreateSwapchainImageView(Image swapchainImage, Format format)
|
||||||
|
@ -306,7 +319,7 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal VulkanCommandBufferPool.VulkanCommandBuffer StartPresentation(VulkanSurfaceRenderTarget renderTarget)
|
internal VulkanCommandBufferPool.VulkanCommandBuffer StartPresentation()
|
||||||
{
|
{
|
||||||
_nextImage = 0;
|
_nextImage = 0;
|
||||||
while (true)
|
while (true)
|
||||||
|
@ -346,8 +359,10 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
|
||||||
internal void BlitImageToCurrentImage(VulkanSurfaceRenderTarget renderTarget, CommandBuffer commandBuffer)
|
internal void BlitImageToCurrentImage(VulkanSurfaceRenderTarget renderTarget, CommandBuffer commandBuffer)
|
||||||
{
|
{
|
||||||
|
var image = renderTarget.GetImage();
|
||||||
|
|
||||||
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
|
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
|
||||||
renderTarget.Image.InternalHandle.Value, (ImageLayout)renderTarget.Image.CurrentLayout,
|
image.InternalHandle.Value, (ImageLayout)image.CurrentLayout,
|
||||||
AccessFlags.AccessNoneKhr,
|
AccessFlags.AccessNoneKhr,
|
||||||
ImageLayout.TransferSrcOptimal,
|
ImageLayout.TransferSrcOptimal,
|
||||||
AccessFlags.AccessTransferReadBit,
|
AccessFlags.AccessTransferReadBit,
|
||||||
|
@ -381,7 +396,7 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_device.Api.CmdBlitImage(commandBuffer, renderTarget.Image.InternalHandle.Value,
|
_device.Api.CmdBlitImage(commandBuffer, image.InternalHandle.Value,
|
||||||
ImageLayout.TransferSrcOptimal,
|
ImageLayout.TransferSrcOptimal,
|
||||||
_swapchainImages[_nextImage],
|
_swapchainImages[_nextImage],
|
||||||
ImageLayout.TransferDstOptimal,
|
ImageLayout.TransferDstOptimal,
|
||||||
|
@ -390,9 +405,9 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
Filter.Linear);
|
Filter.Linear);
|
||||||
|
|
||||||
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
|
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
|
||||||
renderTarget.Image.InternalHandle.Value, ImageLayout.TransferSrcOptimal,
|
image.InternalHandle.Value, ImageLayout.TransferSrcOptimal,
|
||||||
AccessFlags.AccessTransferReadBit,
|
AccessFlags.AccessTransferReadBit,
|
||||||
(ImageLayout)renderTarget.Image.CurrentLayout,
|
(ImageLayout)image.CurrentLayout,
|
||||||
AccessFlags.AccessNoneKhr,
|
AccessFlags.AccessNoneKhr,
|
||||||
renderTarget.MipLevels);
|
renderTarget.MipLevels);
|
||||||
}
|
}
|
||||||
|
@ -434,6 +449,8 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandBufferPool.FreeUsedCommandBuffers();
|
CommandBufferPool.FreeUsedCommandBuffers();
|
||||||
|
|
||||||
|
Presented?.Invoke(this, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,20 +148,18 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
_currentAccessFlags = destinationAccessFlags;
|
_currentAccessFlags = destinationAccessFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TransitionLayout(uint destinationLayout, uint destinationAccessFlags)
|
public void Dispose()
|
||||||
{
|
{
|
||||||
TransitionLayout((ImageLayout)destinationLayout, (AccessFlags)destinationAccessFlags);
|
if (InternalHandle != null)
|
||||||
}
|
{
|
||||||
|
_device.Api.DestroyImageView(_device.InternalHandle, _imageView.Value, Span<AllocationCallbacks>.Empty);
|
||||||
|
_device.Api.DestroyImage(_device.InternalHandle, InternalHandle.Value, Span<AllocationCallbacks>.Empty);
|
||||||
|
_device.Api.FreeMemory(_device.InternalHandle, _imageMemory, Span<AllocationCallbacks>.Empty);
|
||||||
|
|
||||||
public unsafe void Dispose()
|
_imageView = default;
|
||||||
{
|
InternalHandle = null;
|
||||||
_device.Api.DestroyImageView(_device.InternalHandle, _imageView.Value, null);
|
_imageMemory = default;
|
||||||
_device.Api.DestroyImage(_device.InternalHandle, InternalHandle.Value, null);
|
}
|
||||||
_device.Api.FreeMemory(_device.InternalHandle, _imageMemory, null);
|
|
||||||
|
|
||||||
_imageView = default;
|
|
||||||
InternalHandle = default;
|
|
||||||
_imageMemory = default;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,8 +57,7 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
var applicationInfo = new ApplicationInfo
|
var applicationInfo = new ApplicationInfo
|
||||||
{
|
{
|
||||||
PApplicationName = (byte*)applicationName,
|
PApplicationName = (byte*)applicationName,
|
||||||
ApiVersion = new Version32((uint)options.VulkanVersion.Major, (uint)options.VulkanVersion.Minor,
|
ApiVersion = Vk.Version12.Value,
|
||||||
(uint)options.VulkanVersion.Build),
|
|
||||||
PEngineName = (byte*)engineName,
|
PEngineName = (byte*)engineName,
|
||||||
EngineVersion = new Version32(1, 0, 0),
|
EngineVersion = new Version32(1, 0, 0),
|
||||||
ApplicationVersion = new Version32(1, 0, 0)
|
ApplicationVersion = new Version32(1, 0, 0)
|
||||||
|
|
|
@ -11,11 +11,6 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ApplicationName { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Specifies additional extensions to enable if available on the instance
|
/// Specifies additional extensions to enable if available on the instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -18,13 +18,11 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
|
|
||||||
public VulkanPhysicalDevice PhysicalDevice { get; private set; }
|
public VulkanPhysicalDevice PhysicalDevice { get; private set; }
|
||||||
public VulkanInstance Instance { get; }
|
public VulkanInstance Instance { get; }
|
||||||
public VulkanDevice Device { get; set; }
|
|
||||||
public Vk Api { get; private set; }
|
public Vk Api { get; private set; }
|
||||||
public VulkanSurfaceRenderTarget MainSurface { get; set; }
|
public VulkanSurfaceRenderTarget MainSurface { get; set; }
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Device?.Dispose();
|
|
||||||
Instance?.Dispose();
|
Instance?.Dispose();
|
||||||
Api?.Dispose();
|
Api?.Dispose();
|
||||||
}
|
}
|
||||||
|
@ -54,16 +52,9 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
{
|
{
|
||||||
var surface = VulkanSurface.CreateSurface(Instance, platformSurface);
|
var surface = VulkanSurface.CreateSurface(Instance, platformSurface);
|
||||||
|
|
||||||
if (Device == null)
|
if (PhysicalDevice == null)
|
||||||
{
|
{
|
||||||
PhysicalDevice = VulkanPhysicalDevice.FindSuitablePhysicalDevice(Instance, surface, _options.PreferDiscreteGpu, _options.PreferredDevice);
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var renderTarget = new VulkanSurfaceRenderTarget(this, surface);
|
var renderTarget = new VulkanSurfaceRenderTarget(this, surface);
|
||||||
|
@ -71,7 +62,6 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
if (MainSurface == null && surface != null)
|
if (MainSurface == null && surface != null)
|
||||||
{
|
{
|
||||||
MainSurface = renderTarget;
|
MainSurface = renderTarget;
|
||||||
MainSurface.Display.ChangeVSyncMode(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderTarget;
|
return renderTarget;
|
||||||
|
|
|
@ -9,7 +9,6 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
{
|
{
|
||||||
private readonly VulkanDevice _device;
|
private readonly VulkanDevice _device;
|
||||||
private readonly VulkanSurfaceRenderTarget _renderTarget;
|
private readonly VulkanSurfaceRenderTarget _renderTarget;
|
||||||
private VulkanCommandBufferPool.VulkanCommandBuffer _commandBuffer;
|
|
||||||
|
|
||||||
public VulkanSurfaceRenderingSession(VulkanDisplay display, VulkanDevice device,
|
public VulkanSurfaceRenderingSession(VulkanDisplay display, VulkanDevice device,
|
||||||
VulkanSurfaceRenderTarget renderTarget, float scaling)
|
VulkanSurfaceRenderTarget renderTarget, float scaling)
|
||||||
|
@ -32,17 +31,13 @@ namespace Ryujinx.Ava.Ui.Vulkan
|
||||||
{
|
{
|
||||||
if (!Display.EnsureSwapchainAvailable())
|
if (!Display.EnsureSwapchainAvailable())
|
||||||
{
|
{
|
||||||
_renderTarget.Invalidate();
|
_renderTarget.RecreateImage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_commandBuffer = Display.StartPresentation(_renderTarget);
|
_renderTarget.EndDraw();
|
||||||
|
|
||||||
Display.BlitImageToCurrentImage(_renderTarget, _commandBuffer.InternalHandle);
|
|
||||||
|
|
||||||
Display.EndPresentation(_commandBuffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
|
||||||
public override void DestroyBackgroundContext()
|
public override void DestroyBackgroundContext()
|
||||||
{
|
{
|
||||||
_image = null;
|
Image = null;
|
||||||
|
|
||||||
if (_fence != IntPtr.Zero)
|
if (_fence != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
|
@ -57,6 +57,8 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
Image = (int)image;
|
Image = (int)image;
|
||||||
|
|
||||||
|
InvalidateVisual();
|
||||||
}).Wait();
|
}).Wait();
|
||||||
|
|
||||||
if (_fence != IntPtr.Zero)
|
if (_fence != IntPtr.Zero)
|
||||||
|
@ -66,7 +68,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
|
||||||
_fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
|
_fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
|
||||||
|
|
||||||
QueueRender();
|
InvalidateVisual();
|
||||||
|
|
||||||
_gameBackgroundWindow.SwapBuffers();
|
_gameBackgroundWindow.SwapBuffers();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,25 +11,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
{
|
{
|
||||||
internal abstract class RendererControl : Control
|
internal abstract class RendererControl : Control
|
||||||
{
|
{
|
||||||
protected object _image;
|
protected object Image { get; set; }
|
||||||
|
|
||||||
static RendererControl()
|
|
||||||
{
|
|
||||||
AffectsRender<RendererControl>(ImageProperty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly static StyledProperty<object> ImageProperty =
|
|
||||||
AvaloniaProperty.Register<RendererControl, object>(
|
|
||||||
nameof(Image),
|
|
||||||
0,
|
|
||||||
inherits: true,
|
|
||||||
defaultBindingMode: BindingMode.TwoWay);
|
|
||||||
|
|
||||||
protected object Image
|
|
||||||
{
|
|
||||||
get => _image;
|
|
||||||
set => SetAndRaise(ImageProperty, ref _image, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler<EventArgs> RendererInitialized;
|
public event EventHandler<EventArgs> RendererInitialized;
|
||||||
public event EventHandler<Size> SizeChanged;
|
public event EventHandler<Size> SizeChanged;
|
||||||
|
@ -60,8 +42,6 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
if (!rect.IsEmpty)
|
if (!rect.IsEmpty)
|
||||||
{
|
{
|
||||||
RenderSize = rect.Size * VisualRoot.RenderScaling;
|
RenderSize = rect.Size * VisualRoot.RenderScaling;
|
||||||
|
|
||||||
DrawOperation?.Dispose();
|
|
||||||
DrawOperation = CreateDrawOperation();
|
DrawOperation = CreateDrawOperation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,17 +77,11 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
RendererInitialized?.Invoke(this, EventArgs.Empty);
|
RendererInitialized?.Invoke(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueueRender()
|
|
||||||
{
|
|
||||||
Program.RenderTimer.TickNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal abstract void Present(object image);
|
internal abstract void Present(object image);
|
||||||
|
|
||||||
internal void Start()
|
internal void Start()
|
||||||
{
|
{
|
||||||
IsStarted = true;
|
IsStarted = true;
|
||||||
QueueRender();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Stop()
|
internal void Stop()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Media;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Avalonia.Rendering.SceneGraph;
|
using Avalonia.Rendering.SceneGraph;
|
||||||
using Avalonia.Skia;
|
using Avalonia.Skia;
|
||||||
|
@ -11,20 +12,23 @@ using Silk.NET.Vulkan;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using SPB.Windowing;
|
using SPB.Windowing;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Concurrent;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Ui.Controls
|
namespace Ryujinx.Ava.Ui.Controls
|
||||||
{
|
{
|
||||||
internal class VulkanRendererControl : RendererControl
|
internal class VulkanRendererControl : RendererControl
|
||||||
{
|
{
|
||||||
|
private const int MaxImagesInFlight = 3;
|
||||||
|
|
||||||
private VulkanPlatformInterface _platformInterface;
|
private VulkanPlatformInterface _platformInterface;
|
||||||
|
private ConcurrentQueue<PresentImageInfo> _imagesInFlight;
|
||||||
|
private PresentImageInfo _currentImage;
|
||||||
|
|
||||||
public VulkanRendererControl(GraphicsDebugLevel graphicsDebugLevel) : base(graphicsDebugLevel)
|
public VulkanRendererControl(GraphicsDebugLevel graphicsDebugLevel) : base(graphicsDebugLevel)
|
||||||
{
|
{
|
||||||
_platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
_platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
|
|
||||||
|
_imagesInFlight = new ConcurrentQueue<PresentImageInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DestroyBackgroundContext()
|
public override void DestroyBackgroundContext()
|
||||||
|
@ -37,6 +41,40 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
return new VulkanDrawOperation(this);
|
return new VulkanDrawOperation(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
|
||||||
|
_imagesInFlight.Clear();
|
||||||
|
|
||||||
|
if (_platformInterface.MainSurface.Display != null)
|
||||||
|
{
|
||||||
|
_platformInterface.MainSurface.Display.Presented -= Window_Presented;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentImage?.Put();
|
||||||
|
_currentImage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnAttachedToVisualTree(e);
|
||||||
|
|
||||||
|
_platformInterface.MainSurface.Display.Presented += Window_Presented;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Presented(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_platformInterface.MainSurface.Device.QueueWaitIdle();
|
||||||
|
_currentImage?.Put();
|
||||||
|
_currentImage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(DrawingContext context)
|
||||||
|
{
|
||||||
|
base.Render(context);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void CreateWindow()
|
protected override void CreateWindow()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -51,12 +89,29 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
|
||||||
internal override void Present(object image)
|
internal override void Present(object image)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Image = image;
|
||||||
{
|
|
||||||
Image = image;
|
|
||||||
}).Wait();
|
|
||||||
|
|
||||||
QueueRender();
|
_imagesInFlight.Enqueue((PresentImageInfo)image);
|
||||||
|
|
||||||
|
if (_imagesInFlight.Count > MaxImagesInFlight)
|
||||||
|
{
|
||||||
|
_imagesInFlight.TryDequeue(out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dispatcher.UIThread.Post(InvalidateVisual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PresentImageInfo GetImage()
|
||||||
|
{
|
||||||
|
lock (_imagesInFlight)
|
||||||
|
{
|
||||||
|
if (!_imagesInFlight.TryDequeue(out _currentImage))
|
||||||
|
{
|
||||||
|
_currentImage = (PresentImageInfo)Image;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _currentImage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class VulkanDrawOperation : ICustomDrawOperation
|
private class VulkanDrawOperation : ICustomDrawOperation
|
||||||
|
@ -64,6 +119,7 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
public Rect Bounds { get; }
|
public Rect Bounds { get; }
|
||||||
|
|
||||||
private readonly VulkanRendererControl _control;
|
private readonly VulkanRendererControl _control;
|
||||||
|
private bool _isDestroyed;
|
||||||
|
|
||||||
public VulkanDrawOperation(VulkanRendererControl control)
|
public VulkanDrawOperation(VulkanRendererControl control)
|
||||||
{
|
{
|
||||||
|
@ -73,7 +129,12 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
if (_isDestroyed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDestroyed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(ICustomDrawOperation other)
|
public bool Equals(ICustomDrawOperation other)
|
||||||
|
@ -86,30 +147,33 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
return Bounds.Contains(p);
|
return Bounds.Contains(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Render(IDrawingContextImpl context)
|
public unsafe void Render(IDrawingContextImpl context)
|
||||||
{
|
{
|
||||||
if (_control.Image == null || _control.RenderSize.Width == 0 || _control.RenderSize.Height == 0)
|
if (_isDestroyed || _control.Image == null || _control.RenderSize.Width == 0 || _control.RenderSize.Height == 0 ||
|
||||||
|
context is not ISkiaDrawingContextImpl skiaDrawingContextImpl)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var image = (PresentImageInfo)_control.Image;
|
var image = _control.GetImage();
|
||||||
|
|
||||||
if (context is not ISkiaDrawingContextImpl skiaDrawingContextImpl)
|
if (!image.State.IsValid)
|
||||||
{
|
{
|
||||||
|
_control._currentImage = null;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_control._platformInterface.Device.QueueWaitIdle();
|
|
||||||
|
|
||||||
var gpu = AvaloniaLocator.Current.GetService<VulkanSkiaGpu>();
|
var gpu = AvaloniaLocator.Current.GetService<VulkanSkiaGpu>();
|
||||||
|
|
||||||
|
image.Get();
|
||||||
|
|
||||||
var imageInfo = new GRVkImageInfo()
|
var imageInfo = new GRVkImageInfo()
|
||||||
{
|
{
|
||||||
CurrentQueueFamily = _control._platformInterface.PhysicalDevice.QueueFamilyIndex,
|
CurrentQueueFamily = _control._platformInterface.PhysicalDevice.QueueFamilyIndex,
|
||||||
Format = (uint)Format.R8G8B8A8Unorm,
|
Format = (uint)Format.R8G8B8A8Unorm,
|
||||||
Image = image.Image.Handle,
|
Image = image.Image.Handle,
|
||||||
ImageLayout = (uint)ImageLayout.ColorAttachmentOptimal,
|
ImageLayout = (uint)ImageLayout.TransferSrcOptimal,
|
||||||
ImageTiling = (uint)ImageTiling.Optimal,
|
ImageTiling = (uint)ImageTiling.Optimal,
|
||||||
ImageUsageFlags = (uint)(ImageUsageFlags.ImageUsageColorAttachmentBit
|
ImageUsageFlags = (uint)(ImageUsageFlags.ImageUsageColorAttachmentBit
|
||||||
| ImageUsageFlags.ImageUsageTransferSrcBit
|
| ImageUsageFlags.ImageUsageTransferSrcBit
|
||||||
|
@ -127,13 +191,15 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
};
|
};
|
||||||
|
|
||||||
using var backendTexture = new GRBackendRenderTarget(
|
using var backendTexture = new GRBackendRenderTarget(
|
||||||
(int)_control.RenderSize.Width,
|
(int)image.Extent.Width,
|
||||||
(int)_control.RenderSize.Height,
|
(int)image.Extent.Height,
|
||||||
1,
|
1,
|
||||||
imageInfo);
|
imageInfo);
|
||||||
|
|
||||||
|
var vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
|
||||||
|
|
||||||
using var surface = SKSurface.Create(
|
using var surface = SKSurface.Create(
|
||||||
gpu.GrContext,
|
skiaDrawingContextImpl.GrContext,
|
||||||
backendTexture,
|
backendTexture,
|
||||||
GRSurfaceOrigin.TopLeft,
|
GRSurfaceOrigin.TopLeft,
|
||||||
SKColorType.Rgba8888);
|
SKColorType.Rgba8888);
|
||||||
|
@ -143,10 +209,11 @@ namespace Ryujinx.Ava.Ui.Controls
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rect = new Rect(new Point(), _control.RenderSize);
|
var rect = new Rect(new Point(), new Size(image.Extent.Width, image.Extent.Height));
|
||||||
|
|
||||||
using var snapshot = surface.Snapshot();
|
using var snapshot = surface.Snapshot();
|
||||||
skiaDrawingContextImpl.SkCanvas.DrawImage(snapshot, rect.ToSKRect(), _control.Bounds.ToSKRect(), new SKPaint());
|
skiaDrawingContextImpl.SkCanvas.DrawImage(snapshot, rect.ToSKRect(), _control.Bounds.ToSKRect(),
|
||||||
|
new SKPaint());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
{
|
{
|
||||||
internal class MainWindowViewModel : BaseModel
|
internal class MainWindowViewModel : BaseModel
|
||||||
{
|
{
|
||||||
|
private const int HotKeyPressDelayMs = 500;
|
||||||
|
|
||||||
private readonly MainWindow _owner;
|
private readonly MainWindow _owner;
|
||||||
private ObservableCollection<ApplicationData> _applications;
|
private ObservableCollection<ApplicationData> _applications;
|
||||||
private string _aspectStatusText;
|
private string _aspectStatusText;
|
||||||
|
@ -54,6 +56,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
private bool _isLoading;
|
private bool _isLoading;
|
||||||
private int _progressMaximum;
|
private int _progressMaximum;
|
||||||
private int _progressValue;
|
private int _progressValue;
|
||||||
|
private long _lastFullscreenToggle = Environment.TickCount64;
|
||||||
private bool _showLoadProgress;
|
private bool _showLoadProgress;
|
||||||
private bool _showMenuAndStatusBar = true;
|
private bool _showMenuAndStatusBar = true;
|
||||||
private bool _showStatusSeparator;
|
private bool _showStatusSeparator;
|
||||||
|
@ -929,6 +932,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
|
|
||||||
public void ToggleFullscreen()
|
public void ToggleFullscreen()
|
||||||
{
|
{
|
||||||
|
if (Environment.TickCount64 - _lastFullscreenToggle < HotKeyPressDelayMs)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastFullscreenToggle = Environment.TickCount64;
|
||||||
|
|
||||||
WindowState state = _owner.WindowState;
|
WindowState state = _owner.WindowState;
|
||||||
|
|
||||||
if (state == WindowState.FullScreen)
|
if (state == WindowState.FullScreen)
|
||||||
|
@ -1085,6 +1095,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
{
|
{
|
||||||
selection.Favorite = !selection.Favorite;
|
selection.Favorite = !selection.Favorite;
|
||||||
|
|
||||||
|
_owner.ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
|
||||||
|
{
|
||||||
|
appMetadata.Favorite = selection.Favorite;
|
||||||
|
});
|
||||||
|
|
||||||
RefreshView();
|
RefreshView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
private int _graphicsBackendMultithreadingIndex;
|
private int _graphicsBackendMultithreadingIndex;
|
||||||
private float _previousVolumeLevel;
|
private float _previousVolumeLevel;
|
||||||
private float _volume;
|
private float _volume;
|
||||||
|
private bool _isVulkanAvailable = true;
|
||||||
|
private List<string> _gpuIds = new List<string>();
|
||||||
|
private KeyboardHotkeys _keyboardHotkeys;
|
||||||
|
private int _graphicsBackendIndex;
|
||||||
|
|
||||||
public int ResolutionScale
|
public int ResolutionScale
|
||||||
{
|
{
|
||||||
|
@ -97,6 +101,17 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsVulkanAvailable
|
||||||
|
{
|
||||||
|
get => _isVulkanAvailable;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_isVulkanAvailable = value;
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool EnableDiscordIntegration { get; set; }
|
public bool EnableDiscordIntegration { get; set; }
|
||||||
public bool CheckUpdatesOnStart { get; set; }
|
public bool CheckUpdatesOnStart { get; set; }
|
||||||
public bool ShowConfirmExit { get; set; }
|
public bool ShowConfirmExit { get; set; }
|
||||||
|
@ -143,10 +158,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
public int BaseStyleIndex { get; set; }
|
public int BaseStyleIndex { get; set; }
|
||||||
public int GraphicsBackendIndex
|
public int GraphicsBackendIndex
|
||||||
{
|
{
|
||||||
get => graphicsBackendIndex;
|
get => _graphicsBackendIndex;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
graphicsBackendIndex = value;
|
_graphicsBackendIndex = value;
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
OnPropertyChanged(nameof(IsVulkanSelected));
|
OnPropertyChanged(nameof(IsVulkanSelected));
|
||||||
}
|
}
|
||||||
|
@ -170,14 +185,9 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
public DateTimeOffset DateOffset { get; set; }
|
public DateTimeOffset DateOffset { get; set; }
|
||||||
public TimeSpan TimeOffset { get; set; }
|
public TimeSpan TimeOffset { get; set; }
|
||||||
public AvaloniaList<TimeZone> TimeZones { get; set; }
|
public AvaloniaList<TimeZone> TimeZones { get; set; }
|
||||||
|
|
||||||
public AvaloniaList<string> GameDirectories { get; set; }
|
public AvaloniaList<string> GameDirectories { get; set; }
|
||||||
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
||||||
|
|
||||||
private KeyboardHotkeys _keyboardHotkeys;
|
|
||||||
private int graphicsBackendIndex;
|
|
||||||
private List<string> _gpuIds = new List<string>();
|
|
||||||
|
|
||||||
public KeyboardHotkeys KeyboardHotkeys
|
public KeyboardHotkeys KeyboardHotkeys
|
||||||
{
|
{
|
||||||
get => _keyboardHotkeys;
|
get => _keyboardHotkeys;
|
||||||
|
@ -233,20 +243,31 @@ namespace Ryujinx.Ava.Ui.ViewModels
|
||||||
if (!Program.UseVulkan)
|
if (!Program.UseVulkan)
|
||||||
{
|
{
|
||||||
var devices = VulkanRenderer.GetPhysicalDevices();
|
var devices = VulkanRenderer.GetPhysicalDevices();
|
||||||
foreach (var device in devices)
|
|
||||||
|
if (devices.Length == 0)
|
||||||
{
|
{
|
||||||
_gpuIds.Add(device.Id);
|
IsVulkanAvailable = false;
|
||||||
names.Add($"{device.Name} {(device.IsDiscrete ? "(dGpu)" : "")}");
|
GraphicsBackendIndex = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var device in devices)
|
||||||
|
{
|
||||||
|
_gpuIds.Add(device.Id);
|
||||||
|
names.Add($"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var device in VulkanPhysicalDevice.SuitableDevices)
|
foreach (var device in VulkanPhysicalDevice.SuitableDevices)
|
||||||
{
|
{
|
||||||
_gpuIds.Add(VulkanInitialization.StringFromIdPair(device.Value.VendorID, device.Value.DeviceID));
|
_gpuIds.Add(
|
||||||
|
VulkanInitialization.StringFromIdPair(device.Value.VendorID, device.Value.DeviceID));
|
||||||
var value = device.Value;
|
var value = device.Value;
|
||||||
var name = value.DeviceName;
|
var name = value.DeviceName;
|
||||||
names.Add($"{Marshal.PtrToStringAnsi((IntPtr)name)} {(device.Value.DeviceType == PhysicalDeviceType.DiscreteGpu ? "(dGpu)" : "")}");
|
names.Add(
|
||||||
|
$"{Marshal.PtrToStringAnsi((IntPtr)name)} {(device.Value.DeviceType == PhysicalDeviceType.DiscreteGpu ? "(dGPU)" : "")}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -656,7 +656,12 @@ namespace Ryujinx.Ava.Ui.Windows
|
||||||
{
|
{
|
||||||
AppHost = null;
|
AppHost = null;
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(Close);
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
MainContent = null;
|
||||||
|
|
||||||
|
Close();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
AppHost?.Stop();
|
AppHost?.Stop();
|
||||||
|
|
||||||
|
|
|
@ -519,7 +519,7 @@
|
||||||
HorizontalContentAlignment="Left"
|
HorizontalContentAlignment="Left"
|
||||||
ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}"
|
ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}"
|
||||||
SelectedIndex="{Binding GraphicsBackendIndex}">
|
SelectedIndex="{Binding GraphicsBackendIndex}">
|
||||||
<ComboBoxItem>
|
<ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
|
||||||
<TextBlock Text="Vulkan" />
|
<TextBlock Text="Vulkan" />
|
||||||
</ComboBoxItem>
|
</ComboBoxItem>
|
||||||
<ComboBoxItem>
|
<ComboBoxItem>
|
||||||
|
|
|
@ -7,5 +7,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback);
|
void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback);
|
||||||
|
|
||||||
void SetSize(int width, int height);
|
void SetSize(int width, int height);
|
||||||
|
|
||||||
|
void ChangeVSyncMode(bool vsyncEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,5 +30,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||||
{
|
{
|
||||||
_impl.Window.SetSize(width, height);
|
_impl.Window.SetSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ChangeVSyncMode(bool vsyncEnabled) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ChangeVSyncMode(bool vsyncEnabled) { }
|
||||||
|
|
||||||
private void CreateStagingFramebuffer()
|
private void CreateStagingFramebuffer()
|
||||||
{
|
{
|
||||||
_stagingFrameBuffer = GL.GenFramebuffer();
|
_stagingFrameBuffer = GL.GenFramebuffer();
|
||||||
|
|
|
@ -7,7 +7,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
class ImageWindow : WindowBase, IWindow, IDisposable
|
class ImageWindow : WindowBase, IWindow, IDisposable
|
||||||
{
|
{
|
||||||
private const int ImageCount = 5;
|
internal const VkFormat Format = VkFormat.R8G8B8A8Unorm;
|
||||||
|
|
||||||
|
private const int ImageCount = 3;
|
||||||
private const int SurfaceWidth = 1280;
|
private const int SurfaceWidth = 1280;
|
||||||
private const int SurfaceHeight = 720;
|
private const int SurfaceHeight = 720;
|
||||||
|
|
||||||
|
@ -18,52 +20,49 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private Auto<DisposableImage>[] _images;
|
private Auto<DisposableImage>[] _images;
|
||||||
private Auto<DisposableImageView>[] _imageViews;
|
private Auto<DisposableImageView>[] _imageViews;
|
||||||
private Auto<MemoryAllocation>[] _imageAllocationAuto;
|
private Auto<MemoryAllocation>[] _imageAllocationAuto;
|
||||||
|
private ImageState[] _states;
|
||||||
|
private PresentImageInfo[] _presentedImages;
|
||||||
|
private FenceHolder[] _fences;
|
||||||
|
|
||||||
private ulong[] _imageSizes;
|
private ulong[] _imageSizes;
|
||||||
private ulong[] _imageOffsets;
|
private ulong[] _imageOffsets;
|
||||||
|
|
||||||
private Semaphore _imageAvailableSemaphore;
|
|
||||||
private Semaphore _renderFinishedSemaphore;
|
|
||||||
|
|
||||||
private int _width = SurfaceWidth;
|
private int _width = SurfaceWidth;
|
||||||
private int _height = SurfaceHeight;
|
private int _height = SurfaceHeight;
|
||||||
private VkFormat _format;
|
|
||||||
private bool _recreateImages;
|
private bool _recreateImages;
|
||||||
private int _nextImage;
|
private int _nextImage;
|
||||||
|
|
||||||
internal new bool ScreenCaptureRequested { get; set; }
|
|
||||||
|
|
||||||
public unsafe ImageWindow(VulkanRenderer gd, PhysicalDevice physicalDevice, Device device)
|
public unsafe ImageWindow(VulkanRenderer gd, PhysicalDevice physicalDevice, Device device)
|
||||||
{
|
{
|
||||||
_gd = gd;
|
_gd = gd;
|
||||||
_physicalDevice = physicalDevice;
|
_physicalDevice = physicalDevice;
|
||||||
_device = device;
|
_device = device;
|
||||||
|
|
||||||
_format = VkFormat.R8G8B8A8Unorm;
|
|
||||||
|
|
||||||
_images = new Auto<DisposableImage>[ImageCount];
|
_images = new Auto<DisposableImage>[ImageCount];
|
||||||
_imageAllocationAuto = new Auto<MemoryAllocation>[ImageCount];
|
_imageAllocationAuto = new Auto<MemoryAllocation>[ImageCount];
|
||||||
_imageSizes = new ulong[ImageCount];
|
_imageSizes = new ulong[ImageCount];
|
||||||
_imageOffsets = new ulong[ImageCount];
|
_imageOffsets = new ulong[ImageCount];
|
||||||
|
_states = new ImageState[ImageCount];
|
||||||
|
_presentedImages = new PresentImageInfo[ImageCount];
|
||||||
|
|
||||||
CreateImages();
|
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()
|
private void RecreateImages()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < ImageCount; i++)
|
for (int i = 0; i < ImageCount; i++)
|
||||||
{
|
{
|
||||||
_imageViews[i]?.Dispose();
|
lock (_states[i])
|
||||||
_imageAllocationAuto[i]?.Dispose();
|
{
|
||||||
_images[i]?.Dispose();
|
_states[i].IsValid = false;
|
||||||
|
_fences[i]?.Wait();
|
||||||
|
_fences[i]?.Put();
|
||||||
|
_imageViews[i]?.Dispose();
|
||||||
|
_imageAllocationAuto[i]?.Dispose();
|
||||||
|
_images[i]?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_presentedImages = null;
|
||||||
|
|
||||||
CreateImages();
|
CreateImages();
|
||||||
}
|
}
|
||||||
|
@ -71,34 +70,35 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private unsafe void CreateImages()
|
private unsafe void CreateImages()
|
||||||
{
|
{
|
||||||
_imageViews = new Auto<DisposableImageView>[ImageCount];
|
_imageViews = new Auto<DisposableImageView>[ImageCount];
|
||||||
|
_fences = new FenceHolder[ImageCount];
|
||||||
|
_presentedImages = new PresentImageInfo[ImageCount];
|
||||||
|
|
||||||
|
_nextImage = 0;
|
||||||
var cbs = _gd.CommandBufferPool.Rent();
|
var cbs = _gd.CommandBufferPool.Rent();
|
||||||
|
|
||||||
|
var imageCreateInfo = new ImageCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.ImageCreateInfo,
|
||||||
|
ImageType = ImageType.ImageType2D,
|
||||||
|
Format = Format,
|
||||||
|
Extent = new Extent3D((uint?)_width, (uint?)_height, 1),
|
||||||
|
MipLevels = 1,
|
||||||
|
ArrayLayers = 1,
|
||||||
|
Samples = SampleCountFlags.SampleCount1Bit,
|
||||||
|
Tiling = ImageTiling.Optimal,
|
||||||
|
Usage = ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageTransferDstBit,
|
||||||
|
SharingMode = SharingMode.Exclusive,
|
||||||
|
InitialLayout = ImageLayout.Undefined,
|
||||||
|
Flags = ImageCreateFlags.ImageCreateMutableFormatBit
|
||||||
|
};
|
||||||
|
|
||||||
for (int i = 0; i < _images.Length; i++)
|
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();
|
_gd.Api.CreateImage(_device, imageCreateInfo, null, out var image).ThrowOnError();
|
||||||
_images[i] = new Auto<DisposableImage>(new DisposableImage(_gd.Api, _device, image));
|
_images[i] = new Auto<DisposableImage>(new DisposableImage(_gd.Api, _device, image));
|
||||||
|
|
||||||
_gd.Api.GetImageMemoryRequirements(_device, image,
|
_gd.Api.GetImageMemoryRequirements(_device, image,
|
||||||
out var memoryRequirements);
|
out var memoryRequirements);
|
||||||
|
|
||||||
var allocation = _gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, memoryRequirements, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit);
|
var allocation = _gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, memoryRequirements, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit);
|
||||||
|
|
||||||
_imageSizes[i] = allocation.Size;
|
_imageSizes[i] = allocation.Size;
|
||||||
|
@ -108,7 +108,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
_gd.Api.BindImageMemory(_device, image, allocation.Memory, allocation.Offset);
|
_gd.Api.BindImageMemory(_device, image, allocation.Memory, allocation.Offset);
|
||||||
|
|
||||||
_imageViews[i] = CreateImageView(image, _format);
|
_imageViews[i] = CreateImageView(image, Format);
|
||||||
|
|
||||||
Transition(
|
Transition(
|
||||||
cbs.CommandBuffer,
|
cbs.CommandBuffer,
|
||||||
|
@ -116,7 +116,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
ImageLayout.Undefined,
|
ImageLayout.Undefined,
|
||||||
ImageLayout.ColorAttachmentOptimal);
|
ImageLayout.TransferSrcOptimal);
|
||||||
|
|
||||||
|
_states[i] = new ImageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
_gd.CommandBufferPool.Return(cbs);
|
_gd.CommandBufferPool.Return(cbs);
|
||||||
|
@ -165,7 +167,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
image.GetUnsafe().Value,
|
image.GetUnsafe().Value,
|
||||||
0,
|
0,
|
||||||
AccessFlags.AccessTransferWriteBit,
|
AccessFlags.AccessTransferWriteBit,
|
||||||
ImageLayout.ColorAttachmentOptimal,
|
ImageLayout.TransferSrcOptimal,
|
||||||
ImageLayout.General);
|
ImageLayout.General);
|
||||||
|
|
||||||
var view = (TextureView)texture;
|
var view = (TextureView)texture;
|
||||||
|
@ -232,7 +234,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_imageViews[_nextImage],
|
_imageViews[_nextImage],
|
||||||
_width,
|
_width,
|
||||||
_height,
|
_height,
|
||||||
_format,
|
Format,
|
||||||
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
||||||
new Extents2D(dstX0, dstY1, dstX1, dstY0),
|
new Extents2D(dstX0, dstY1, dstX1, dstY0),
|
||||||
true,
|
true,
|
||||||
|
@ -244,7 +246,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
ImageLayout.General,
|
ImageLayout.General,
|
||||||
ImageLayout.ColorAttachmentOptimal);
|
ImageLayout.TransferSrcOptimal);
|
||||||
|
|
||||||
_gd.CommandBufferPool.Return(
|
_gd.CommandBufferPool.Return(
|
||||||
cbs,
|
cbs,
|
||||||
|
@ -252,12 +254,30 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
stackalloc[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit },
|
stackalloc[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit },
|
||||||
null);
|
null);
|
||||||
|
|
||||||
var memory = _imageAllocationAuto[_nextImage].GetUnsafe().Memory;
|
_fences[_nextImage]?.Put();
|
||||||
var presentInfo = new PresentImageInfo(image.GetUnsafe().Value, memory, _imageSizes[_nextImage], _imageOffsets[_nextImage], _renderFinishedSemaphore, _imageAvailableSemaphore);
|
_fences[_nextImage] = cbs.GetFence();
|
||||||
|
cbs.GetFence().Get();
|
||||||
|
|
||||||
swapBuffersCallback(presentInfo);
|
PresentImageInfo info = _presentedImages[_nextImage];
|
||||||
|
|
||||||
_nextImage %= ImageCount;
|
if (info == null)
|
||||||
|
{
|
||||||
|
info = new PresentImageInfo(
|
||||||
|
image,
|
||||||
|
_imageAllocationAuto[_nextImage],
|
||||||
|
_device,
|
||||||
|
_physicalDevice,
|
||||||
|
_imageSizes[_nextImage],
|
||||||
|
_imageOffsets[_nextImage],
|
||||||
|
new Extent2D((uint)_width, (uint)_height),
|
||||||
|
_states[_nextImage]);
|
||||||
|
|
||||||
|
_presentedImages[_nextImage] = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
swapBuffersCallback(info);
|
||||||
|
|
||||||
|
_nextImage = (_nextImage + 1) % ImageCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void Transition(
|
private unsafe void Transition(
|
||||||
|
@ -320,11 +340,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphore, null);
|
|
||||||
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphore, null);
|
|
||||||
|
|
||||||
for (int i = 0; i < ImageCount; i++)
|
for (int i = 0; i < ImageCount; i++)
|
||||||
{
|
{
|
||||||
|
_states[i].IsValid = false;
|
||||||
|
_fences[i]?.Wait();
|
||||||
|
_fences[i]?.Put();
|
||||||
_imageViews[i]?.Dispose();
|
_imageViews[i]?.Dispose();
|
||||||
_imageAllocationAuto[i]?.Dispose();
|
_imageAllocationAuto[i]?.Dispose();
|
||||||
_images[i]?.Dispose();
|
_images[i]?.Dispose();
|
||||||
|
@ -337,25 +357,73 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void ChangeVSyncMode(bool vsyncEnabled) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImageState
|
||||||
|
{
|
||||||
|
private bool _isValid = true;
|
||||||
|
|
||||||
|
public bool IsValid
|
||||||
|
{
|
||||||
|
get => _isValid;
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
_isValid = value;
|
||||||
|
|
||||||
|
StateChanged?.Invoke(this, _isValid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler<bool> StateChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PresentImageInfo
|
public class PresentImageInfo
|
||||||
{
|
{
|
||||||
public Image Image { get; }
|
private readonly Auto<DisposableImage> _image;
|
||||||
public DeviceMemory Memory { get; }
|
private readonly Auto<MemoryAllocation> _memory;
|
||||||
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)
|
public Image Image => _image.GetUnsafe().Value;
|
||||||
|
|
||||||
|
public DeviceMemory Memory => _memory.GetUnsafe().Memory;
|
||||||
|
|
||||||
|
public Device Device { get; }
|
||||||
|
public PhysicalDevice PhysicalDevice { get; }
|
||||||
|
public ulong MemorySize { get; }
|
||||||
|
public ulong MemoryOffset { get; }
|
||||||
|
public Extent2D Extent { get; }
|
||||||
|
public ImageState State { get; internal set; }
|
||||||
|
internal PresentImageInfo(
|
||||||
|
Auto<DisposableImage> image,
|
||||||
|
Auto<MemoryAllocation> memory,
|
||||||
|
Device device,
|
||||||
|
PhysicalDevice physicalDevice,
|
||||||
|
ulong memorySize,
|
||||||
|
ulong memoryOffset,
|
||||||
|
Extent2D extent2D,
|
||||||
|
ImageState state)
|
||||||
{
|
{
|
||||||
this.Image = image;
|
_image = image;
|
||||||
this.Memory = memory;
|
_memory = memory;
|
||||||
this.MemorySize = memorySize;
|
Device = device;
|
||||||
this.MemoryOffset = memoryOffset;
|
PhysicalDevice = physicalDevice;
|
||||||
this.ReadySemaphore = readySemaphore;
|
MemorySize = memorySize;
|
||||||
this.AvailableSemaphore = availableSemaphore;
|
MemoryOffset = memoryOffset;
|
||||||
|
Extent = extent2D;
|
||||||
|
State = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Get()
|
||||||
|
{
|
||||||
|
_memory.IncrementReferenceCount();
|
||||||
|
_image.IncrementReferenceCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Put()
|
||||||
|
{
|
||||||
|
_memory.DecrementReferenceCount();
|
||||||
|
_image.DecrementReferenceCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -25,6 +25,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
private int _width;
|
private int _width;
|
||||||
private int _height;
|
private int _height;
|
||||||
|
private bool _vsyncEnabled;
|
||||||
|
private bool _vsyncModeChanged;
|
||||||
private VkFormat _format;
|
private VkFormat _format;
|
||||||
|
|
||||||
public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
|
public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
|
||||||
|
@ -47,6 +49,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
private void RecreateSwapchain()
|
private void RecreateSwapchain()
|
||||||
{
|
{
|
||||||
|
_vsyncModeChanged = false;
|
||||||
|
|
||||||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||||||
{
|
{
|
||||||
_swapchainImageViews[i].Dispose();
|
_swapchainImageViews[i].Dispose();
|
||||||
|
@ -110,7 +114,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
ImageArrayLayers = 1,
|
ImageArrayLayers = 1,
|
||||||
PreTransform = capabilities.CurrentTransform,
|
PreTransform = capabilities.CurrentTransform,
|
||||||
CompositeAlpha = CompositeAlphaFlagsKHR.CompositeAlphaOpaqueBitKhr,
|
CompositeAlpha = CompositeAlphaFlagsKHR.CompositeAlphaOpaqueBitKhr,
|
||||||
PresentMode = ChooseSwapPresentMode(presentModes),
|
PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled),
|
||||||
Clipped = true,
|
Clipped = true,
|
||||||
OldSwapchain = oldSwapchain
|
OldSwapchain = oldSwapchain
|
||||||
};
|
};
|
||||||
|
@ -178,9 +182,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return availableFormats[0];
|
return availableFormats[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes)
|
private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, bool vsyncEnabled)
|
||||||
{
|
{
|
||||||
if (availablePresentModes.Contains(PresentModeKHR.PresentModeImmediateKhr))
|
if (!vsyncEnabled && availablePresentModes.Contains(PresentModeKHR.PresentModeImmediateKhr))
|
||||||
{
|
{
|
||||||
return PresentModeKHR.PresentModeImmediateKhr;
|
return PresentModeKHR.PresentModeImmediateKhr;
|
||||||
}
|
}
|
||||||
|
@ -188,6 +192,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
return PresentModeKHR.PresentModeMailboxKhr;
|
return PresentModeKHR.PresentModeMailboxKhr;
|
||||||
}
|
}
|
||||||
|
else if (availablePresentModes.Contains(PresentModeKHR.PresentModeFifoKhr))
|
||||||
|
{
|
||||||
|
return PresentModeKHR.PresentModeFifoKhr;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return PresentModeKHR.PresentModeFifoKhr;
|
return PresentModeKHR.PresentModeFifoKhr;
|
||||||
|
@ -224,7 +232,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
ref nextImage);
|
ref nextImage);
|
||||||
|
|
||||||
if (acquireResult == Result.ErrorOutOfDateKhr ||
|
if (acquireResult == Result.ErrorOutOfDateKhr ||
|
||||||
acquireResult == Result.SuboptimalKhr)
|
acquireResult == Result.SuboptimalKhr ||
|
||||||
|
_vsyncModeChanged)
|
||||||
{
|
{
|
||||||
RecreateSwapchain();
|
RecreateSwapchain();
|
||||||
}
|
}
|
||||||
|
@ -404,6 +413,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
// Not needed as we can get the size from the surface.
|
// Not needed as we can get the size from the surface.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void ChangeVSyncMode(bool vsyncEnabled)
|
||||||
|
{
|
||||||
|
_vsyncEnabled = vsyncEnabled;
|
||||||
|
_vsyncModeChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
|
|
@ -10,5 +10,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
public abstract void Dispose();
|
public abstract void Dispose();
|
||||||
public abstract void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback);
|
public abstract void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback);
|
||||||
public abstract void SetSize(int width, int height);
|
public abstract void SetSize(int width, int height);
|
||||||
|
public abstract void ChangeVSyncMode(bool vsyncEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -402,6 +402,8 @@ namespace Ryujinx.Ui
|
||||||
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
||||||
Translator.IsReadyForTranslation.Set();
|
Translator.IsReadyForTranslation.Set();
|
||||||
|
|
||||||
|
Renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync);
|
||||||
|
|
||||||
(Toplevel as MainWindow)?.ActivatePauseMenu();
|
(Toplevel as MainWindow)?.ActivatePauseMenu();
|
||||||
|
|
||||||
while (_isActive)
|
while (_isActive)
|
||||||
|
|
Reference in a new issue