aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ryujinx.Ava/AppHost.cs39
-rw-r--r--Ryujinx.Ava/Program.cs13
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs3
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs78
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs67
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/Surfaces/VulkanSurfaceRenderTarget.cs96
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs51
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs10
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs47
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs22
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs3
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs5
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs12
-rw-r--r--Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurfaceRenderingSession.cs9
-rw-r--r--Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs6
-rw-r--r--Ryujinx.Ava/Ui/Controls/RendererControl.cs28
-rw-r--r--Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs107
-rw-r--r--Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs15
-rw-r--r--Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs45
-rw-r--r--Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs7
-rw-r--r--Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml2
-rw-r--r--Ryujinx.Graphics.GAL/IWindow.cs2
-rw-r--r--Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs2
-rw-r--r--Ryujinx.Graphics.OpenGL/Window.cs2
-rw-r--r--Ryujinx.Graphics.Vulkan/ImageWindow.cs198
-rw-r--r--Ryujinx.Graphics.Vulkan/Window.cs23
-rw-r--r--Ryujinx.Graphics.Vulkan/WindowBase.cs1
-rw-r--r--Ryujinx/Ui/RendererWidgetBase.cs2
28 files changed, 584 insertions, 311 deletions
diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs
index bd9c808e..7e3cddc8 100644
--- a/Ryujinx.Ava/AppHost.cs
+++ b/Ryujinx.Ava/AppHost.cs
@@ -12,6 +12,7 @@ using Ryujinx.Audio.Integration;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input;
+using Ryujinx.Ava.Ui.Backend.Vulkan;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.Vulkan;
@@ -334,6 +335,8 @@ namespace Ryujinx.Ava
return;
}
+ AvaloniaLocator.Current.GetService<VulkanPlatformInterface>()?.MainSurface.Display.ChangeVSyncMode(true);
+
_isStopped = true;
_isActive = false;
}
@@ -596,12 +599,13 @@ namespace Ryujinx.Ava
if (Program.UseVulkan)
{
var vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
+
renderer = new VulkanRenderer(vulkan.Instance.InternalHandle,
- vulkan.Device.InternalHandle,
+ vulkan.MainSurface.Device.InternalHandle,
vulkan.PhysicalDevice.InternalHandle,
- vulkan.Device.Queue.InternalHandle,
+ vulkan.MainSurface.Device.Queue.InternalHandle,
vulkan.PhysicalDevice.QueueFamilyIndex,
- vulkan.Device.Lock);
+ vulkan.MainSurface.Device.Lock);
}
else
{
@@ -775,7 +779,10 @@ namespace Ryujinx.Ava
Width = (int)e.Width;
Height = (int)e.Height;
- SetRendererWindowSize(e);
+ if (!Program.UseVulkan)
+ {
+ SetRendererWindowSize(e);
+ }
}
private void MainLoop()
@@ -815,12 +822,11 @@ namespace Ryujinx.Ava
_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);
@@ -837,8 +843,6 @@ namespace Ryujinx.Ava
Renderer.Start();
- Renderer.QueueRender();
-
while (_isActive)
{
if (Device.WaitFifo())
@@ -889,6 +893,16 @@ namespace Ryujinx.Ava
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
$"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);
}
@@ -970,6 +984,9 @@ namespace Ryujinx.Ava
{
case KeyboardHotkeyState.ToggleVSync:
Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
+
+ AvaloniaLocator.Current.GetService<VulkanPlatformInterface>()?.MainSurface?.Display?.ChangeVSyncMode(Device.EnableDeviceVsync);
+
break;
case KeyboardHotkeyState.Screenshot:
ScreenshotRequested = true;
diff --git a/Ryujinx.Ava/Program.cs b/Ryujinx.Ava/Program.cs
index be27e9cd..4a546747 100644
--- a/Ryujinx.Ava/Program.cs
+++ b/Ryujinx.Ava/Program.cs
@@ -94,7 +94,6 @@ namespace Ryujinx.Ava
.With(new Ui.Vulkan.VulkanOptions()
{
ApplicationName = "Ryujinx.Graphics.Vulkan",
- VulkanVersion = new Version(1, 2),
MaxQueueCount = 2,
PreferDiscreteGpu = true,
PreferredDevice = !PreviewerDetached ? "" : ConfigurationState.Instance.Graphics.PreferredGpu.Value,
@@ -183,6 +182,18 @@ namespace Ryujinx.Ava
if (UseVulkan)
{
+ if (VulkanRenderer.GetPhysicalDevices().Length == 0)
+ {
+ UseVulkan = false;
+
+ ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
+
+ Logger.Warning?.PrintMsg(LogClass.Application, "A suitable Vulkan physical device is not available. Falling back to OpenGL");
+ }
+ }
+
+ if (UseVulkan)
+ {
// With a custom gpu backend, avalonia doesn't enable dpi awareness, so the backend must handle it. This isn't so for the opengl backed,
// as that uses avalonia's gpu backend and it's enabled there.
ForceDpiAware.Windows();
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs
index b1326dbf..2a1cd229 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/ResultExtensions.cs
@@ -7,7 +7,8 @@ namespace Ryujinx.Ava.Ui.Vulkan
{
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}\".");
}
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs
index ba7ddc7a..70ec39c7 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanRenderTarget.cs
@@ -1,26 +1,90 @@
using System;
+using Avalonia;
using Avalonia.Skia;
using Ryujinx.Ava.Ui.Vulkan;
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
+using Silk.NET.Vulkan;
using SkiaSharp;
namespace Ryujinx.Ava.Ui.Backend.Vulkan
{
internal class VulkanRenderTarget : ISkiaGpuRenderTarget
{
- public GRContext GrContext { get; set; }
+ public GRContext GrContext { get; private set; }
private readonly VulkanSurfaceRenderTarget _surface;
+ private readonly VulkanPlatformInterface _vulkanPlatformInterface;
private readonly IVulkanPlatformSurface _vulkanPlatformSurface;
+ private GRVkBackendContext _grVkBackend;
public VulkanRenderTarget(VulkanPlatformInterface vulkanPlatformInterface, IVulkanPlatformSurface vulkanPlatformSurface)
{
_surface = vulkanPlatformInterface.CreateRenderTarget(vulkanPlatformSurface);
+ _vulkanPlatformInterface = vulkanPlatformInterface;
_vulkanPlatformSurface = vulkanPlatformSurface;
+
+ Initialize();
+ }
+
+ private void Initialize()
+ {
+ GRVkGetProcedureAddressDelegate getProc = GetVulkanProcAddress;
+
+ _grVkBackend = new GRVkBackendContext()
+ {
+ VkInstance = _surface.Device.Handle,
+ VkPhysicalDevice = _vulkanPlatformInterface.PhysicalDevice.Handle,
+ VkDevice = _surface.Device.Handle,
+ VkQueue = _surface.Device.Queue.Handle,
+ GraphicsQueueIndex = _vulkanPlatformInterface.PhysicalDevice.QueueFamilyIndex,
+ GetProcedureAddress = getProc
+ };
+
+ GrContext = GRContext.CreateVulkan(_grVkBackend);
+
+ var gpu = AvaloniaLocator.Current.GetService<VulkanSkiaGpu>();
+
+ if (gpu.MaxResourceBytes.HasValue)
+ {
+ GrContext.SetResourceCacheLimit(gpu.MaxResourceBytes.Value);
+ }
+ }
+
+ private IntPtr GetVulkanProcAddress(string name, IntPtr instanceHandle, IntPtr deviceHandle)
+ {
+ IntPtr addr;
+
+ if (deviceHandle != IntPtr.Zero)
+ {
+ addr = _vulkanPlatformInterface.Api.GetDeviceProcAddr(new Device(deviceHandle), name);
+
+ if (addr != IntPtr.Zero)
+ {
+ return addr;
+ }
+
+ addr = _vulkanPlatformInterface.Api.GetDeviceProcAddr(new Device(_surface.Device.Handle), name);
+
+ if (addr != IntPtr.Zero)
+ {
+ return addr;
+ }
+ }
+
+ addr = _vulkanPlatformInterface.Api.GetInstanceProcAddr(new Instance(_vulkanPlatformInterface.Instance.Handle), name);
+
+ if (addr == IntPtr.Zero)
+ {
+ addr = _vulkanPlatformInterface.Api.GetInstanceProcAddr(new Instance(instanceHandle), name);
+ }
+
+ return addr;
}
public void Dispose()
{
+ _grVkBackend.Dispose();
+ GrContext.Dispose();
_surface.Dispose();
}
@@ -45,20 +109,22 @@ namespace Ryujinx.Ava.Ui.Backend.Vulkan
{
GrContext.ResetContext();
+ var image = _surface.GetImage();
+
var imageInfo = new GRVkImageInfo()
{
CurrentQueueFamily = disp.QueueFamilyIndex,
- Format = _surface.ImageFormat,
- Image = _surface.Image.Handle,
- ImageLayout = (uint)_surface.Image.CurrentLayout,
- ImageTiling = (uint)_surface.Image.Tiling,
+ Format = (uint)image.Format,
+ Image = image.Handle,
+ ImageLayout = (uint)image.CurrentLayout,
+ ImageTiling = (uint)image.Tiling,
ImageUsageFlags = _surface.UsageFlags,
LevelCount = _surface.MipLevels,
SampleCount = 1,
Protected = false,
Alloc = new GRVkAlloc()
{
- Memory = _surface.Image.MemoryHandle,
+ Memory = image.MemoryHandle,
Flags = 0,
Offset = 0,
Size = _surface.MemorySize
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs
index 4fc6b929..a5c27086 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/Skia/VulkanSkiaGpu.cs
@@ -13,71 +13,12 @@ namespace Ryujinx.Ava.Ui.Backend.Vulkan
public class VulkanSkiaGpu : ISkiaGpu
{
private readonly VulkanPlatformInterface _vulkan;
- private readonly long? _maxResourceBytes;
- private GRVkBackendContext _grVkBackend;
- private bool _initialized;
-
- public GRContext GrContext { get; private set; }
+ public long? MaxResourceBytes { get; }
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);
- }
+ MaxResourceBytes = maxResourceBytes;
}
public ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces)
@@ -106,10 +47,6 @@ namespace Ryujinx.Ava.Ui.Backend.Vulkan
VulkanRenderTarget vulkanRenderTarget = new VulkanRenderTarget(_vulkan, window);
- Initialize();
-
- vulkanRenderTarget.GrContext = GrContext;
-
return vulkanRenderTarget;
}
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/Surfaces/VulkanSurfaceRenderTarget.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/Surfaces/VulkanSurfaceRenderTarget.cs
index b2b8843d..510e6724 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/Surfaces/VulkanSurfaceRenderTarget.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/Surfaces/VulkanSurfaceRenderTarget.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia;
+using Ryujinx.Graphics.Vulkan;
using Silk.NET.Vulkan;
namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
@@ -7,24 +8,35 @@ namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
internal class VulkanSurfaceRenderTarget : IDisposable
{
private readonly VulkanPlatformInterface _platformInterface;
-
private readonly Format _format;
- public VulkanImage Image { get; private set; }
- public bool IsCorrupted { get; private set; } = true;
+ private VulkanCommandBufferPool.VulkanCommandBuffer _commandBuffer;
+ private VulkanImage Image { get; set; }
+ private object _lock = new object();
public uint MipLevels => Image.MipLevels;
+ public VulkanDevice Device { get; }
public VulkanSurfaceRenderTarget(VulkanPlatformInterface platformInterface, VulkanSurface surface)
{
_platformInterface = platformInterface;
- Display = VulkanDisplay.CreateDisplay(platformInterface.Instance, platformInterface.Device,
- platformInterface.PhysicalDevice, surface);
+ var device = VulkanInitialization.CreateDevice(platformInterface.Api,
+ platformInterface.PhysicalDevice.InternalHandle,
+ platformInterface.PhysicalDevice.QueueFamilyIndex,
+ VulkanInitialization.GetSupportedExtensions(platformInterface.Api, platformInterface.PhysicalDevice.InternalHandle),
+ platformInterface.PhysicalDevice.QueueCount);
+
+ Device = new VulkanDevice(device, platformInterface.PhysicalDevice, platformInterface.Api);
+
+ Display = VulkanDisplay.CreateDisplay(
+ platformInterface.Instance,
+ Device,
+ platformInterface.PhysicalDevice,
+ surface);
Surface = surface;
// Skia seems to only create surfaces from images with unorm format
-
IsRgba = Display.SurfaceFormat.Format >= Format.R8G8B8A8Unorm &&
Display.SurfaceFormat.Format <= Format.R8G8B8A8Srgb;
@@ -33,13 +45,13 @@ namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
public bool IsRgba { get; }
- public uint ImageFormat => (uint) _format;
+ public uint ImageFormat => (uint)_format;
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;
@@ -47,46 +59,76 @@ namespace Ryujinx.Ava.Ui.Vulkan.Surfaces
public void Dispose()
{
- _platformInterface.Device.WaitIdle();
- DestroyImage();
- Display?.Dispose();
- Surface?.Dispose();
+ lock (_lock)
+ {
+ DestroyImage();
+ Display?.Dispose();
+ Surface?.Dispose();
+ Device?.Dispose();
+
+ Display = null;
+ Surface = null;
+ }
}
public VulkanSurfaceRenderingSession BeginDraw(float scaling)
{
- var session = new VulkanSurfaceRenderingSession(Display, _platformInterface.Device, this, scaling);
-
- if (IsCorrupted)
+ if (Image == null)
{
- IsCorrupted = false;
- DestroyImage();
- CreateImage();
- }
- else
- {
- Image.TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr);
+ RecreateImage();
}
+ _commandBuffer?.WaitForFence();
+ _commandBuffer = null;
+
+ var session = new VulkanSurfaceRenderingSession(Display, Device, this, scaling);
+
+ Image.TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr);
+
return session;
}
- public void Invalidate()
+ public void RecreateImage()
{
- IsCorrupted = true;
+ DestroyImage();
+ CreateImage();
}
private void CreateImage()
{
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()
{
- _platformInterface.Device.WaitIdle();
+ _commandBuffer?.WaitForFence();
+ _commandBuffer = null;
Image?.Dispose();
+ Image = null;
+ }
+
+ public VulkanImage GetImage()
+ {
+ return Image;
+ }
+
+ public void EndDraw()
+ {
+ lock (_lock)
+ {
+ if (Display == null)
+ {
+ return;
+ }
+
+ _commandBuffer = Display.StartPresentation();
+
+ Display.BlitImageToCurrentImage(this, _commandBuffer.InternalHandle);
+
+ Display.EndPresentation(_commandBuffer);
+ }
}
}
}
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs
index 240035ca..a00ecf2b 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanCommandBufferPool.cs
@@ -10,6 +10,7 @@ namespace Ryujinx.Ava.Ui.Vulkan
private readonly CommandPool _commandPool;
private readonly List<VulkanCommandBuffer> _usedCommandBuffers = new();
+ private readonly object _lock = new object();
public unsafe VulkanCommandBufferPool(VulkanDevice device, VulkanPhysicalDevice physicalDevice)
{
@@ -36,9 +37,12 @@ namespace Ryujinx.Ava.Ui.Vulkan
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()
@@ -48,7 +52,7 @@ namespace Ryujinx.Ava.Ui.Vulkan
public void FreeUsedCommandBuffers()
{
- lock (_usedCommandBuffers)
+ lock (_lock)
{
foreach (var usedCommandBuffer in _usedCommandBuffers)
{
@@ -61,7 +65,7 @@ namespace Ryujinx.Ava.Ui.Vulkan
private void DisposeCommandBuffer(VulkanCommandBuffer commandBuffer)
{
- lock (_usedCommandBuffers)
+ lock (_lock)
{
_usedCommandBuffers.Add(commandBuffer);
}
@@ -69,8 +73,11 @@ namespace Ryujinx.Ava.Ui.Vulkan
public void Dispose()
{
- FreeUsedCommandBuffers();
- _device.Api.DestroyCommandPool(_device.InternalHandle, _commandPool, Span<AllocationCallbacks>.Empty);
+ lock (_lock)
+ {
+ FreeUsedCommandBuffers();
+ _device.Api.DestroyCommandPool(_device.InternalHandle, _commandPool, Span<AllocationCallbacks>.Empty);
+ }
}
public class VulkanCommandBuffer : IDisposable
@@ -80,6 +87,8 @@ namespace Ryujinx.Ava.Ui.Vulkan
private readonly Fence _fence;
private bool _hasEnded;
private bool _hasStarted;
+ private bool _isDisposed;
+ private object _lock = new object();
public IntPtr Handle => InternalHandle.Handle;
@@ -101,6 +110,22 @@ namespace Ryujinx.Ava.Ui.Vulkan
device.Api.CreateFence(device.InternalHandle, fenceCreateInfo, null, out _fence);
}
+ public void WaitForFence()
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ lock (_lock)
+ {
+ if (!_isDisposed)
+ {
+ _device.Api.WaitForFences(_device.InternalHandle, 1, _fence, true, ulong.MaxValue);
+ }
+ }
+ }
+
public void BeginRecording()
{
if (!_hasStarted)
@@ -173,9 +198,17 @@ namespace Ryujinx.Ava.Ui.Vulkan
public 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, Span<AllocationCallbacks>.Empty);
+ lock (_lock)
+ {
+ if (!_isDisposed)
+ {
+ _isDisposed = true;
+
+ _device.Api.WaitForFences(_device.InternalHandle, 1, _fence, true, ulong.MaxValue);
+ _device.Api.FreeCommandBuffers(_device.InternalHandle, _commandBufferPool._commandPool, 1, InternalHandle);
+ _device.Api.DestroyFence(_device.InternalHandle, _fence, Span<AllocationCallbacks>.Empty);
+ }
+ }
}
}
}
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs
index b03fd720..3d893e19 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDevice.cs
@@ -14,12 +14,9 @@ namespace Ryujinx.Ava.Ui.Vulkan
api.GetDeviceQueue(apiHandle, physicalDevice.QueueFamilyIndex, 0, out var queue);
- var vulkanQueue = new VulkanQueue(this, queue);
- Queue = vulkanQueue;
+ Queue = new VulkanQueue(this, queue);
- PresentQueue = vulkanQueue;
-
- CommandBufferPool = new VulkanCommandBufferPool(this, physicalDevice);
+ PresentQueue = Queue;
}
public IntPtr Handle => InternalHandle.Handle;
@@ -29,13 +26,12 @@ namespace Ryujinx.Ava.Ui.Vulkan
public VulkanQueue Queue { get; private set; }
public VulkanQueue PresentQueue { get; }
- public VulkanCommandBufferPool CommandBufferPool { get; }
public void Dispose()
{
WaitIdle();
- CommandBufferPool?.Dispose();
Queue = null;
+ Api.DestroyDevice(InternalHandle, Span<AllocationCallbacks>.Empty);
}
internal void Submit(SubmitInfo submitInfo, Fence fence = default)
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs
index bfe5b5a6..f3116fbd 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs
@@ -3,7 +3,6 @@ using System.Linq;
using System.Threading;
using Avalonia;
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
-using Ryujinx.Ui.Common.Configuration;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
@@ -15,16 +14,19 @@ namespace Ryujinx.Ava.Ui.Vulkan
private readonly VulkanInstance _instance;
private readonly VulkanPhysicalDevice _physicalDevice;
private readonly VulkanSemaphorePair _semaphorePair;
+ private readonly VulkanDevice _device;
private uint _nextImage;
private readonly VulkanSurface _surface;
private SurfaceFormatKHR _surfaceFormat;
private SwapchainKHR _swapchain;
private Extent2D _swapchainExtent;
private Image[] _swapchainImages;
- private VulkanDevice _device { get; }
- private ImageView[] _swapchainImageViews = new ImageView[0];
+ private ImageView[] _swapchainImageViews = Array.Empty<ImageView>();
private bool _vsyncStateChanged;
private bool _vsyncEnabled;
+ private bool _surfaceChanged;
+
+ public event EventHandler Presented;
public VulkanCommandBufferPool CommandBufferPool { get; set; }
@@ -73,6 +75,14 @@ namespace Ryujinx.Ava.Ui.Vulkan
CommandBufferPool.Dispose();
}
+ public bool IsSurfaceChanged()
+ {
+ var changed = _surfaceChanged;
+ _surfaceChanged = false;
+
+ return changed;
+ }
+
private static unsafe SwapchainKHR CreateSwapchain(VulkanInstance instance, VulkanDevice device,
VulkanPhysicalDevice physicalDevice, VulkanSurface surface, out Extent2D swapchainExtent,
SwapchainKHR? oldswapchain = null, bool vsyncEnabled = true)
@@ -193,22 +203,23 @@ namespace Ryujinx.Ava.Ui.Vulkan
}
var modes = presentModes.ToList();
- var presentMode = PresentModeKHR.PresentModeFifoKhr;
if (!vsyncEnabled && modes.Contains(PresentModeKHR.PresentModeImmediateKhr))
{
- presentMode = PresentModeKHR.PresentModeImmediateKhr;
+ return PresentModeKHR.PresentModeImmediateKhr;
}
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,
@@ -266,6 +277,8 @@ namespace Ryujinx.Ava.Ui.Vulkan
_swapchain = CreateSwapchain(_instance, _device, _physicalDevice, _surface, out _swapchainExtent, _swapchain, _vsyncEnabled);
CreateSwapchainImages();
+
+ _surfaceChanged = true;
}
private unsafe ImageView CreateSwapchainImageView(Image swapchainImage, Format format)
@@ -306,7 +319,7 @@ namespace Ryujinx.Ava.Ui.Vulkan
return true;
}
- internal VulkanCommandBufferPool.VulkanCommandBuffer StartPresentation(VulkanSurfaceRenderTarget renderTarget)
+ internal VulkanCommandBufferPool.VulkanCommandBuffer StartPresentation()
{
_nextImage = 0;
while (true)
@@ -346,8 +359,10 @@ namespace Ryujinx.Ava.Ui.Vulkan
internal void BlitImageToCurrentImage(VulkanSurfaceRenderTarget renderTarget, CommandBuffer commandBuffer)
{
+ var image = renderTarget.GetImage();
+
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
- renderTarget.Image.InternalHandle.Value, (ImageLayout)renderTarget.Image.CurrentLayout,
+ image.InternalHandle.Value, (ImageLayout)image.CurrentLayout,
AccessFlags.AccessNoneKhr,
ImageLayout.TransferSrcOptimal,
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,
_swapchainImages[_nextImage],
ImageLayout.TransferDstOptimal,
@@ -390,9 +405,9 @@ namespace Ryujinx.Ava.Ui.Vulkan
Filter.Linear);
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
- renderTarget.Image.InternalHandle.Value, ImageLayout.TransferSrcOptimal,
+ image.InternalHandle.Value, ImageLayout.TransferSrcOptimal,
AccessFlags.AccessTransferReadBit,
- (ImageLayout)renderTarget.Image.CurrentLayout,
+ (ImageLayout)image.CurrentLayout,
AccessFlags.AccessNoneKhr,
renderTarget.MipLevels);
}
@@ -434,6 +449,8 @@ namespace Ryujinx.Ava.Ui.Vulkan
}
CommandBufferPool.FreeUsedCommandBuffers();
+
+ Presented?.Invoke(this, null);
}
}
}
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs
index 343ba760..3fbb8665 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanImage.cs
@@ -148,20 +148,18 @@ namespace Ryujinx.Ava.Ui.Vulkan
_currentAccessFlags = destinationAccessFlags;
}
- public void TransitionLayout(uint destinationLayout, uint destinationAccessFlags)
+ public void Dispose()
{
- 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);
+ if (InternalHandle != null)
+ {
+ _device.Api.DestroyImageView(_device.InternalHandle, _imageView.Value, Span<AllocationCallbacks>.Empty);
+ _device.Api.DestroyImage(_device.InternalHandle, InternalHandle.Value, Span<AllocationCallbacks>.Empty);
+ _device.Api.FreeMemory(_device.InternalHandle, _imageMemory, Span<AllocationCallbacks>.Empty);
- _imageView = default;
- InternalHandle = default;
- _imageMemory = default;
+ _imageView = default;
+ InternalHandle = null;
+ _imageMemory = default;
+ }
}
}
}
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs
index a3a9ea61..b50e9c07 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanInstance.cs
@@ -57,8 +57,7 @@ namespace Ryujinx.Ava.Ui.Vulkan
var applicationInfo = new ApplicationInfo
{
PApplicationName = (byte*)applicationName,
- ApiVersion = new Version32((uint)options.VulkanVersion.Major, (uint)options.VulkanVersion.Minor,
- (uint)options.VulkanVersion.Build),
+ ApiVersion = Vk.Version12.Value,
PEngineName = (byte*)engineName,
EngineVersion = new Version32(1, 0, 0),
ApplicationVersion = new Version32(1, 0, 0)
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs
index 8e836398..0027753c 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanOptions.cs
@@ -12,11 +12,6 @@ namespace Ryujinx.Ava.Ui.Vulkan
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 IEnumerable<string> InstanceExtensions { get; set; } = Enumerable.Empty<string>();
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs
index 47a07949..ff8d9328 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanPlatformInterface.cs
@@ -18,13 +18,11 @@ namespace Ryujinx.Ava.Ui.Vulkan
public VulkanPhysicalDevice PhysicalDevice { get; private set; }
public VulkanInstance Instance { get; }
- public VulkanDevice Device { get; set; }
public Vk Api { get; private set; }
public VulkanSurfaceRenderTarget MainSurface { get; set; }
public void Dispose()
{
- Device?.Dispose();
Instance?.Dispose();
Api?.Dispose();
}
@@ -54,16 +52,9 @@ namespace Ryujinx.Ava.Ui.Vulkan
{
var surface = VulkanSurface.CreateSurface(Instance, platformSurface);
- if (Device == null)
+ if (PhysicalDevice == 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);
}
var renderTarget = new VulkanSurfaceRenderTarget(this, surface);
@@ -71,7 +62,6 @@ namespace Ryujinx.Ava.Ui.Vulkan
if (MainSurface == null && surface != null)
{
MainSurface = renderTarget;
- MainSurface.Display.ChangeVSyncMode(false);
}
return renderTarget;
diff --git a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurfaceRenderingSession.cs b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurfaceRenderingSession.cs
index 8833ede5..71f5f18a 100644
--- a/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurfaceRenderingSession.cs
+++ b/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanSurfaceRenderingSession.cs
@@ -9,7 +9,6 @@ namespace Ryujinx.Ava.Ui.Vulkan
{
private readonly VulkanDevice _device;
private readonly VulkanSurfaceRenderTarget _renderTarget;
- private VulkanCommandBufferPool.VulkanCommandBuffer _commandBuffer;
public VulkanSurfaceRenderingSession(VulkanDisplay display, VulkanDevice device,
VulkanSurfaceRenderTarget renderTarget, float scaling)
@@ -32,17 +31,13 @@ namespace Ryujinx.Ava.Ui.Vulkan
{
if (!Display.EnsureSwapchainAvailable())
{
- _renderTarget.Invalidate();
+ _renderTarget.RecreateImage();
}
}
public void Dispose()
{
- _commandBuffer = Display.StartPresentation(_renderTarget);
-
- Display.BlitImageToCurrentImage(_renderTarget, _commandBuffer.InternalHandle);
-
- Display.EndPresentation(_commandBuffer);
+ _renderTarget.EndDraw();
}
}
}
diff --git a/Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs b/Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs
index db9caca1..e58bdaa0 100644
--- a/Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs
+++ b/Ryujinx.Ava/Ui/Controls/OpenGLRendererControl.cs
@@ -37,7 +37,7 @@ namespace Ryujinx.Ava.Ui.Controls
public override void DestroyBackgroundContext()
{
- _image = null;
+ Image = null;
if (_fence != IntPtr.Zero)
{
@@ -57,6 +57,8 @@ namespace Ryujinx.Ava.Ui.Controls
Dispatcher.UIThread.InvokeAsync(() =>
{
Image = (int)image;
+
+ InvalidateVisual();
}).Wait();
if (_fence != IntPtr.Zero)
@@ -66,7 +68,7 @@ namespace Ryujinx.Ava.Ui.Controls
_fence = GL.FenceSync(SyncCondition.SyncGpuCommandsComplete, WaitSyncFlags.None);
- QueueRender();
+ InvalidateVisual();
_gameBackgroundWindow.SwapBuffers();
}
diff --git a/Ryujinx.Ava/Ui/Controls/RendererControl.cs b/Ryujinx.Ava/Ui/Controls/RendererControl.cs
index 130348f2..392f67e3 100644
--- a/Ryujinx.Ava/Ui/Controls/RendererControl.cs
+++ b/Ryujinx.Ava/Ui/Controls/RendererControl.cs
@@ -11,25 +11,7 @@ namespace Ryujinx.Ava.Ui.Controls
{
internal abstract class RendererControl : Control
{
- protected object _image;
-
- 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);
- }
+ protected object Image { get; set; }
public event EventHandler<EventArgs> RendererInitialized;
public event EventHandler<Size> SizeChanged;
@@ -60,8 +42,6 @@ namespace Ryujinx.Ava.Ui.Controls
if (!rect.IsEmpty)
{
RenderSize = rect.Size * VisualRoot.RenderScaling;
-
- DrawOperation?.Dispose();
DrawOperation = CreateDrawOperation();
}
}
@@ -97,17 +77,11 @@ namespace Ryujinx.Ava.Ui.Controls
RendererInitialized?.Invoke(this, EventArgs.Empty);
}
- public void QueueRender()
- {
- Program.RenderTimer.TickNow();
- }
-
internal abstract void Present(object image);
internal void Start()
{
IsStarted = true;
- QueueRender();
}
internal void Stop()
diff --git a/Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs b/Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs
index fdbd8df9..7b7dfaa1 100644
--- a/Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs
+++ b/Ryujinx.Ava/Ui/Controls/VulkanRendererControl.cs
@@ -1,4 +1,5 @@
using Avalonia;
+using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Skia;
@@ -11,20 +12,23 @@ 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;
+using System.Collections.Concurrent;
namespace Ryujinx.Ava.Ui.Controls
{
internal class VulkanRendererControl : RendererControl
{
+ private const int MaxImagesInFlight = 3;
+
private VulkanPlatformInterface _platformInterface;
+ private ConcurrentQueue<PresentImageInfo> _imagesInFlight;
+ private PresentImageInfo _currentImage;
public VulkanRendererControl(GraphicsDebugLevel graphicsDebugLevel) : base(graphicsDebugLevel)
{
_platformInterface = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
+
+ _imagesInFlight = new ConcurrentQueue<PresentImageInfo>();
}
public override void DestroyBackgroundContext()
@@ -37,6 +41,40 @@ namespace Ryujinx.Ava.Ui.Controls
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()
{
}
@@ -51,12 +89,29 @@ namespace Ryujinx.Ava.Ui.Controls
internal override void Present(object image)
{
- Dispatcher.UIThread.InvokeAsync(() =>
+ Image = image;
+
+ _imagesInFlight.Enqueue((PresentImageInfo)image);
+
+ if (_imagesInFlight.Count > MaxImagesInFlight)
+ {
+ _imagesInFlight.TryDequeue(out _);
+ }
+
+ Dispatcher.UIThread.Post(InvalidateVisual);
+ }
+
+ private PresentImageInfo GetImage()
+ {
+ lock (_imagesInFlight)
{
- Image = image;
- }).Wait();
+ if (!_imagesInFlight.TryDequeue(out _currentImage))
+ {
+ _currentImage = (PresentImageInfo)Image;
+ }
- QueueRender();
+ return _currentImage;
+ }
}
private class VulkanDrawOperation : ICustomDrawOperation
@@ -64,6 +119,7 @@ namespace Ryujinx.Ava.Ui.Controls
public Rect Bounds { get; }
private readonly VulkanRendererControl _control;
+ private bool _isDestroyed;
public VulkanDrawOperation(VulkanRendererControl control)
{
@@ -73,7 +129,12 @@ namespace Ryujinx.Ava.Ui.Controls
public void Dispose()
{
+ if (_isDestroyed)
+ {
+ return;
+ }
+ _isDestroyed = true;
}
public bool Equals(ICustomDrawOperation other)
@@ -86,30 +147,33 @@ namespace Ryujinx.Ava.Ui.Controls
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;
}
- var image = (PresentImageInfo)_control.Image;
+ var image = _control.GetImage();
- if (context is not ISkiaDrawingContextImpl skiaDrawingContextImpl)
+ if (!image.State.IsValid)
{
+ _control._currentImage = null;
+
return;
}
- _control._platformInterface.Device.QueueWaitIdle();
-
var gpu = AvaloniaLocator.Current.GetService<VulkanSkiaGpu>();
+ image.Get();
+
var imageInfo = new GRVkImageInfo()
{
CurrentQueueFamily = _control._platformInterface.PhysicalDevice.QueueFamilyIndex,
Format = (uint)Format.R8G8B8A8Unorm,
Image = image.Image.Handle,
- ImageLayout = (uint)ImageLayout.ColorAttachmentOptimal,
+ ImageLayout = (uint)ImageLayout.TransferSrcOptimal,
ImageTiling = (uint)ImageTiling.Optimal,
ImageUsageFlags = (uint)(ImageUsageFlags.ImageUsageColorAttachmentBit
| ImageUsageFlags.ImageUsageTransferSrcBit
@@ -127,13 +191,15 @@ namespace Ryujinx.Ava.Ui.Controls
};
using var backendTexture = new GRBackendRenderTarget(
- (int)_control.RenderSize.Width,
- (int)_control.RenderSize.Height,
+ (int)image.Extent.Width,
+ (int)image.Extent.Height,
1,
imageInfo);
+
+ var vulkan = AvaloniaLocator.Current.GetService<VulkanPlatformInterface>();
using var surface = SKSurface.Create(
- gpu.GrContext,
+ skiaDrawingContextImpl.GrContext,
backendTexture,
GRSurfaceOrigin.TopLeft,
SKColorType.Rgba8888);
@@ -143,10 +209,11 @@ namespace Ryujinx.Ava.Ui.Controls
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();
- skiaDrawingContextImpl.SkCanvas.DrawImage(snapshot, rect.ToSKRect(), _control.Bounds.ToSKRect(), new SKPaint());
+ skiaDrawingContextImpl.SkCanvas.DrawImage(snapshot, rect.ToSKRect(), _control.Bounds.ToSKRect(),
+ new SKPaint());
}
}
}
diff --git a/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs b/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs
index cd756d68..98516159 100644
--- a/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs
+++ b/Ryujinx.Ava/Ui/ViewModels/MainWindowViewModel.cs
@@ -38,6 +38,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
internal class MainWindowViewModel : BaseModel
{
+ private const int HotKeyPressDelayMs = 500;
+
private readonly MainWindow _owner;
private ObservableCollection<ApplicationData> _applications;
private string _aspectStatusText;
@@ -54,6 +56,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
private bool _isLoading;
private int _progressMaximum;
private int _progressValue;
+ private long _lastFullscreenToggle = Environment.TickCount64;
private bool _showLoadProgress;
private bool _showMenuAndStatusBar = true;
private bool _showStatusSeparator;
@@ -929,6 +932,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void ToggleFullscreen()
{
+ if (Environment.TickCount64 - _lastFullscreenToggle < HotKeyPressDelayMs)
+ {
+ return;
+ }
+
+ _lastFullscreenToggle = Environment.TickCount64;
+
WindowState state = _owner.WindowState;
if (state == WindowState.FullScreen)
@@ -1085,6 +1095,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
selection.Favorite = !selection.Favorite;
+ _owner.ApplicationLibrary.LoadAndSaveMetaData(selection.TitleId, appMetadata =>
+ {
+ appMetadata.Favorite = selection.Favorite;
+ });
+
RefreshView();
}
}
diff --git a/Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs b/Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs
index 7b08923e..a7cf710e 100644
--- a/Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs
+++ b/Ryujinx.Ava/Ui/ViewModels/SettingsViewModel.cs
@@ -48,6 +48,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
private int _graphicsBackendMultithreadingIndex;
private float _previousVolumeLevel;
private float _volume;
+ private bool _isVulkanAvailable = true;
+ private List<string> _gpuIds = new List<string>();
+ private KeyboardHotkeys _keyboardHotkeys;
+ private int _graphicsBackendIndex;
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 CheckUpdatesOnStart { get; set; }
public bool ShowConfirmExit { get; set; }
@@ -143,10 +158,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
public int BaseStyleIndex { get; set; }
public int GraphicsBackendIndex
{
- get => graphicsBackendIndex;
+ get => _graphicsBackendIndex;
set
{
- graphicsBackendIndex = value;
+ _graphicsBackendIndex = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsVulkanSelected));
}
@@ -170,14 +185,9 @@ namespace Ryujinx.Ava.Ui.ViewModels
public DateTimeOffset DateOffset { get; set; }
public TimeSpan TimeOffset { get; set; }
public AvaloniaList<TimeZone> TimeZones { get; set; }
-
public AvaloniaList<string> GameDirectories { get; set; }
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
- private KeyboardHotkeys _keyboardHotkeys;
- private int graphicsBackendIndex;
- private List<string> _gpuIds = new List<string>();
-
public KeyboardHotkeys KeyboardHotkeys
{
get => _keyboardHotkeys;
@@ -233,20 +243,31 @@ namespace Ryujinx.Ava.Ui.ViewModels
if (!Program.UseVulkan)
{
var devices = VulkanRenderer.GetPhysicalDevices();
- foreach (var device in devices)
+
+ if (devices.Length == 0)
{
- _gpuIds.Add(device.Id);
- names.Add($"{device.Name} {(device.IsDiscrete ? "(dGpu)" : "")}");
+ IsVulkanAvailable = false;
+ GraphicsBackendIndex = 1;
+ }
+ else
+ {
+ foreach (var device in devices)
+ {
+ _gpuIds.Add(device.Id);
+ names.Add($"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}");
+ }
}
}
else
{
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 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)" : "")}");
}
}
diff --git a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs
index 73e5e099..ef22e5c8 100644
--- a/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs
+++ b/Ryujinx.Ava/Ui/Windows/MainWindow.axaml.cs
@@ -656,7 +656,12 @@ namespace Ryujinx.Ava.Ui.Windows
{
AppHost = null;
- Dispatcher.UIThread.Post(Close);
+ Dispatcher.UIThread.Post(() =>
+ {
+ MainContent = null;
+
+ Close();
+ });
};
AppHost?.Stop();
diff --git a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml
index 0e5978b7..b4629760 100644
--- a/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml
+++ b/Ryujinx.Ava/Ui/Windows/SettingsWindow.axaml
@@ -519,7 +519,7 @@
HorizontalContentAlignment="Left"
ToolTip.Tip="{locale:Locale SettingsTabGraphicsBackendTooltip}"
SelectedIndex="{Binding GraphicsBackendIndex}">
- <ComboBoxItem>
+ <ComboBoxItem IsVisible="{Binding IsVulkanAvailable}">
<TextBlock Text="Vulkan" />
</ComboBoxItem>
<ComboBoxItem>
diff --git a/Ryujinx.Graphics.GAL/IWindow.cs b/Ryujinx.Graphics.GAL/IWindow.cs
index 12ff1deb..043193c9 100644
--- a/Ryujinx.Graphics.GAL/IWindow.cs
+++ b/Ryujinx.Graphics.GAL/IWindow.cs
@@ -7,5 +7,7 @@ namespace Ryujinx.Graphics.GAL
void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback);
void SetSize(int width, int height);
+
+ void ChangeVSyncMode(bool vsyncEnabled);
}
}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs b/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs
index c8045502..21a66e7c 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs
@@ -30,5 +30,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
{
_impl.Window.SetSize(width, height);
}
+
+ public void ChangeVSyncMode(bool vsyncEnabled) { }
}
}
diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs
index e5a7ebf0..f67c6a72 100644
--- a/Ryujinx.Graphics.OpenGL/Window.cs
+++ b/Ryujinx.Graphics.OpenGL/Window.cs
@@ -58,6 +58,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
}
+ public void ChangeVSyncMode(bool vsyncEnabled) { }
+
private void CreateStagingFramebuffer()
{
_stagingFrameBuffer = GL.GenFramebuffer();
diff --git a/Ryujinx.Graphics.Vulkan/ImageWindow.cs b/Ryujinx.Graphics.Vulkan/ImageWindow.cs
index 5dd23155..69302fdf 100644
--- a/Ryujinx.Graphics.Vulkan/ImageWindow.cs
+++ b/Ryujinx.Graphics.Vulkan/ImageWindow.cs
@@ -7,7 +7,9 @@ namespace Ryujinx.Graphics.Vulkan
{
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 SurfaceHeight = 720;
@@ -18,52 +20,49 @@ namespace Ryujinx.Graphics.Vulkan
private Auto<DisposableImage>[] _images;
private Auto<DisposableImageView>[] _imageViews;
private Auto<MemoryAllocation>[] _imageAllocationAuto;
+ private ImageState[] _states;
+ private PresentImageInfo[] _presentedImages;
+ private FenceHolder[] _fences;
+
private ulong[] _imageSizes;
private ulong[] _imageOffsets;
- private 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(VulkanRenderer 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];
+ _states = new ImageState[ImageCount];
+ _presentedImages = new PresentImageInfo[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();
+ lock (_states[i])
+ {
+ _states[i].IsValid = false;
+ _fences[i]?.Wait();
+ _fences[i]?.Put();
+ _imageViews[i]?.Dispose();
+ _imageAllocationAuto[i]?.Dispose();
+ _images[i]?.Dispose();
+ }
}
+ _presentedImages = null;
CreateImages();
}
@@ -71,34 +70,35 @@ namespace Ryujinx.Graphics.Vulkan
private unsafe void CreateImages()
{
_imageViews = new Auto<DisposableImageView>[ImageCount];
+ _fences = new FenceHolder[ImageCount];
+ _presentedImages = new PresentImageInfo[ImageCount];
+ _nextImage = 0;
var cbs = _gd.CommandBufferPool.Rent();
- for (int i = 0; i < _images.Length; i++)
+
+ var imageCreateInfo = new ImageCreateInfo
{
- 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
- };
+ SType = StructureType.ImageCreateInfo,
+ ImageType = ImageType.ImageType2D,
+ Format = Format,
+ Extent = new Extent3D((uint?)_width, (uint?)_height, 1),
+ MipLevels = 1,
+ ArrayLayers = 1,
+ Samples = SampleCountFlags.SampleCount1Bit,
+ Tiling = ImageTiling.Optimal,
+ Usage = ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageTransferDstBit,
+ SharingMode = SharingMode.Exclusive,
+ InitialLayout = ImageLayout.Undefined,
+ Flags = ImageCreateFlags.ImageCreateMutableFormatBit
+ };
+ for (int i = 0; i < _images.Length; i++)
+ {
_gd.Api.CreateImage(_device, imageCreateInfo, null, out var image).ThrowOnError();
_images[i] = new Auto<DisposableImage>(new DisposableImage(_gd.Api, _device, image));
_gd.Api.GetImageMemoryRequirements(_device, image,
out var memoryRequirements);
-
var allocation = _gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, memoryRequirements, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit);
_imageSizes[i] = allocation.Size;
@@ -108,7 +108,7 @@ namespace Ryujinx.Graphics.Vulkan
_gd.Api.BindImageMemory(_device, image, allocation.Memory, allocation.Offset);
- _imageViews[i] = CreateImageView(image, _format);
+ _imageViews[i] = CreateImageView(image, Format);
Transition(
cbs.CommandBuffer,
@@ -116,7 +116,9 @@ namespace Ryujinx.Graphics.Vulkan
0,
0,
ImageLayout.Undefined,
- ImageLayout.ColorAttachmentOptimal);
+ ImageLayout.TransferSrcOptimal);
+
+ _states[i] = new ImageState();
}
_gd.CommandBufferPool.Return(cbs);
@@ -165,7 +167,7 @@ namespace Ryujinx.Graphics.Vulkan
image.GetUnsafe().Value,
0,
AccessFlags.AccessTransferWriteBit,
- ImageLayout.ColorAttachmentOptimal,
+ ImageLayout.TransferSrcOptimal,
ImageLayout.General);
var view = (TextureView)texture;
@@ -232,7 +234,7 @@ namespace Ryujinx.Graphics.Vulkan
_imageViews[_nextImage],
_width,
_height,
- _format,
+ Format,
new Extents2D(srcX0, srcY0, srcX1, srcY1),
new Extents2D(dstX0, dstY1, dstX1, dstY0),
true,
@@ -244,7 +246,7 @@ namespace Ryujinx.Graphics.Vulkan
0,
0,
ImageLayout.General,
- ImageLayout.ColorAttachmentOptimal);
+ ImageLayout.TransferSrcOptimal);
_gd.CommandBufferPool.Return(
cbs,
@@ -252,12 +254,30 @@ namespace Ryujinx.Graphics.Vulkan
stackalloc[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit },
null);
- var memory = _imageAllocationAuto[_nextImage].GetUnsafe().Memory;
- var presentInfo = new PresentImageInfo(image.GetUnsafe().Value, memory, _imageSizes[_nextImage], _imageOffsets[_nextImage], _renderFinishedSemaphore, _imageAvailableSemaphore);
+ _fences[_nextImage]?.Put();
+ _fences[_nextImage] = cbs.GetFence();
+ cbs.GetFence().Get();
+
+ PresentImageInfo info = _presentedImages[_nextImage];
+
+ if (info == null)
+ {
+ info = new PresentImageInfo(
+ image,
+ _imageAllocationAuto[_nextImage],
+ _device,
+ _physicalDevice,
+ _imageSizes[_nextImage],
+ _imageOffsets[_nextImage],
+ new Extent2D((uint)_width, (uint)_height),
+ _states[_nextImage]);
+
+ _presentedImages[_nextImage] = info;
+ }
- swapBuffersCallback(presentInfo);
+ swapBuffersCallback(info);
- _nextImage %= ImageCount;
+ _nextImage = (_nextImage + 1) % ImageCount;
}
private unsafe void Transition(
@@ -320,11 +340,11 @@ namespace Ryujinx.Graphics.Vulkan
{
unsafe
{
- _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphore, null);
- _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphore, null);
-
for (int i = 0; i < ImageCount; i++)
{
+ _states[i].IsValid = false;
+ _fences[i]?.Wait();
+ _fences[i]?.Put();
_imageViews[i]?.Dispose();
_imageAllocationAuto[i]?.Dispose();
_images[i]?.Dispose();
@@ -337,25 +357,73 @@ namespace Ryujinx.Graphics.Vulkan
{
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 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)
+ private readonly Auto<DisposableImage> _image;
+ private readonly Auto<MemoryAllocation> _memory;
+
+ public Image Image => _image.GetUnsafe().Value;
+
+ public DeviceMemory Memory => _memory.GetUnsafe().Memory;
+
+ public Device Device { get; }
+ public PhysicalDevice PhysicalDevice { get; }
+ public ulong MemorySize { get; }
+ public ulong MemoryOffset { get; }
+ public Extent2D Extent { get; }
+ public ImageState State { get; internal set; }
+ internal PresentImageInfo(
+ Auto<DisposableImage> image,
+ Auto<MemoryAllocation> memory,
+ Device device,
+ PhysicalDevice physicalDevice,
+ ulong memorySize,
+ ulong memoryOffset,
+ Extent2D extent2D,
+ ImageState state)
+ {
+ _image = image;
+ _memory = memory;
+ Device = device;
+ PhysicalDevice = physicalDevice;
+ MemorySize = memorySize;
+ MemoryOffset = memoryOffset;
+ Extent = extent2D;
+ State = state;
+ }
+
+ public void Get()
+ {
+ _memory.IncrementReferenceCount();
+ _image.IncrementReferenceCount();
+ }
+
+ public void Put()
{
- this.Image = image;
- this.Memory = memory;
- this.MemorySize = memorySize;
- this.MemoryOffset = memoryOffset;
- this.ReadySemaphore = readySemaphore;
- this.AvailableSemaphore = availableSemaphore;
+ _memory.DecrementReferenceCount();
+ _image.DecrementReferenceCount();
}
}
} \ No newline at end of file
diff --git a/Ryujinx.Graphics.Vulkan/Window.cs b/Ryujinx.Graphics.Vulkan/Window.cs
index 12212a7f..26f53b39 100644
--- a/Ryujinx.Graphics.Vulkan/Window.cs
+++ b/Ryujinx.Graphics.Vulkan/Window.cs
@@ -25,6 +25,8 @@ namespace Ryujinx.Graphics.Vulkan
private int _width;
private int _height;
+ private bool _vsyncEnabled;
+ private bool _vsyncModeChanged;
private VkFormat _format;
public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
@@ -47,6 +49,8 @@ namespace Ryujinx.Graphics.Vulkan
private void RecreateSwapchain()
{
+ _vsyncModeChanged = false;
+
for (int i = 0; i < _swapchainImageViews.Length; i++)
{
_swapchainImageViews[i].Dispose();
@@ -110,7 +114,7 @@ namespace Ryujinx.Graphics.Vulkan
ImageArrayLayers = 1,
PreTransform = capabilities.CurrentTransform,
CompositeAlpha = CompositeAlphaFlagsKHR.CompositeAlphaOpaqueBitKhr,
- PresentMode = ChooseSwapPresentMode(presentModes),
+ PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled),
Clipped = true,
OldSwapchain = oldSwapchain
};
@@ -178,9 +182,9 @@ namespace Ryujinx.Graphics.Vulkan
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;
}
@@ -188,6 +192,10 @@ namespace Ryujinx.Graphics.Vulkan
{
return PresentModeKHR.PresentModeMailboxKhr;
}
+ else if (availablePresentModes.Contains(PresentModeKHR.PresentModeFifoKhr))
+ {
+ return PresentModeKHR.PresentModeFifoKhr;
+ }
else
{
return PresentModeKHR.PresentModeFifoKhr;
@@ -224,7 +232,8 @@ namespace Ryujinx.Graphics.Vulkan
ref nextImage);
if (acquireResult == Result.ErrorOutOfDateKhr ||
- acquireResult == Result.SuboptimalKhr)
+ acquireResult == Result.SuboptimalKhr ||
+ _vsyncModeChanged)
{
RecreateSwapchain();
}
@@ -404,6 +413,12 @@ namespace Ryujinx.Graphics.Vulkan
// 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)
{
if (disposing)
diff --git a/Ryujinx.Graphics.Vulkan/WindowBase.cs b/Ryujinx.Graphics.Vulkan/WindowBase.cs
index 4f1f0d16..80b5c0e3 100644
--- a/Ryujinx.Graphics.Vulkan/WindowBase.cs
+++ b/Ryujinx.Graphics.Vulkan/WindowBase.cs
@@ -10,5 +10,6 @@ namespace Ryujinx.Graphics.Vulkan
public abstract void Dispose();
public abstract void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback);
public abstract void SetSize(int width, int height);
+ public abstract void ChangeVSyncMode(bool vsyncEnabled);
}
} \ No newline at end of file
diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs
index 22e8d5c3..3cdc424e 100644
--- a/Ryujinx/Ui/RendererWidgetBase.cs
+++ b/Ryujinx/Ui/RendererWidgetBase.cs
@@ -402,6 +402,8 @@ namespace Ryujinx.Ui
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
Translator.IsReadyForTranslation.Set();
+ Renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync);
+
(Toplevel as MainWindow)?.ActivatePauseMenu();
while (_isActive)