R/Ryujinx.Ava/Ui/Backend/Vulkan/VulkanDisplay.cs
Emmanuel Hansen c8f9292bab
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
2022-08-16 16:32:37 +00:00

456 lines
17 KiB
C#

using System;
using System.Linq;
using System.Threading;
using Avalonia;
using Ryujinx.Ava.Ui.Vulkan.Surfaces;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
namespace Ryujinx.Ava.Ui.Vulkan
{
internal class VulkanDisplay : IDisposable
{
private static KhrSwapchain _swapchainExtension;
private readonly VulkanInstance _instance;
private readonly VulkanPhysicalDevice _physicalDevice;
private readonly VulkanSemaphorePair _semaphorePair;
private readonly VulkanDevice _device;
private uint _nextImage;
private readonly VulkanSurface _surface;
private SurfaceFormatKHR _surfaceFormat;
private SwapchainKHR _swapchain;
private Extent2D _swapchainExtent;
private Image[] _swapchainImages;
private ImageView[] _swapchainImageViews = Array.Empty<ImageView>();
private bool _vsyncStateChanged;
private bool _vsyncEnabled;
private bool _surfaceChanged;
public event EventHandler Presented;
public VulkanCommandBufferPool CommandBufferPool { get; set; }
public object Lock => _device.Lock;
private VulkanDisplay(VulkanInstance instance, VulkanDevice device,
VulkanPhysicalDevice physicalDevice, VulkanSurface surface, SwapchainKHR swapchain,
Extent2D swapchainExtent)
{
_instance = instance;
_device = device;
_physicalDevice = physicalDevice;
_swapchain = swapchain;
_swapchainExtent = swapchainExtent;
_surface = surface;
CreateSwapchainImages();
_semaphorePair = new VulkanSemaphorePair(_device);
CommandBufferPool = new VulkanCommandBufferPool(device, physicalDevice);
}
public PixelSize Size { get; private set; }
public uint QueueFamilyIndex => _physicalDevice.QueueFamilyIndex;
internal SurfaceFormatKHR SurfaceFormat
{
get
{
if (_surfaceFormat.Format == Format.Undefined)
{
_surfaceFormat = _surface.GetSurfaceFormat(_physicalDevice);
}
return _surfaceFormat;
}
}
public void Dispose()
{
_device.WaitIdle();
_semaphorePair?.Dispose();
DestroyCurrentImageViews();
_swapchainExtension.DestroySwapchain(_device.InternalHandle, _swapchain, Span<AllocationCallbacks>.Empty);
CommandBufferPool.Dispose();
}
public bool IsSurfaceChanged()
{
var changed = _surfaceChanged;
_surfaceChanged = false;
return changed;
}
private static unsafe SwapchainKHR CreateSwapchain(VulkanInstance instance, VulkanDevice device,
VulkanPhysicalDevice physicalDevice, VulkanSurface surface, out Extent2D swapchainExtent,
SwapchainKHR? oldswapchain = null, bool vsyncEnabled = true)
{
if (_swapchainExtension == null)
{
instance.Api.TryGetDeviceExtension(instance.InternalHandle, device.InternalHandle, out _swapchainExtension);
}
while (!surface.CanSurfacePresent(physicalDevice))
{
Thread.Sleep(16);
}
VulkanSurface.SurfaceExtension.GetPhysicalDeviceSurfaceCapabilities(physicalDevice.InternalHandle,
surface.ApiHandle, out var capabilities);
var imageCount = capabilities.MinImageCount + 1;
if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount)
{
imageCount = capabilities.MaxImageCount;
}
var surfaceFormat = surface.GetSurfaceFormat(physicalDevice);
bool supportsIdentityTransform = capabilities.SupportedTransforms.HasFlag(SurfaceTransformFlagsKHR.SurfaceTransformIdentityBitKhr);
bool isRotated = capabilities.CurrentTransform.HasFlag(SurfaceTransformFlagsKHR.SurfaceTransformRotate90BitKhr) ||
capabilities.CurrentTransform.HasFlag(SurfaceTransformFlagsKHR.SurfaceTransformRotate270BitKhr);
swapchainExtent = GetSwapchainExtent(surface, capabilities);
CompositeAlphaFlagsKHR compositeAlphaFlags = GetSuitableCompositeAlphaFlags(capabilities);
PresentModeKHR presentMode = GetSuitablePresentMode(physicalDevice, surface, vsyncEnabled);
var swapchainCreateInfo = new SwapchainCreateInfoKHR
{
SType = StructureType.SwapchainCreateInfoKhr,
Surface = surface.ApiHandle,
MinImageCount = imageCount,
ImageFormat = surfaceFormat.Format,
ImageColorSpace = surfaceFormat.ColorSpace,
ImageExtent = swapchainExtent,
ImageUsage =
ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit,
ImageSharingMode = SharingMode.Exclusive,
ImageArrayLayers = 1,
PreTransform = supportsIdentityTransform && isRotated ?
SurfaceTransformFlagsKHR.SurfaceTransformIdentityBitKhr :
capabilities.CurrentTransform,
CompositeAlpha = compositeAlphaFlags,
PresentMode = presentMode,
Clipped = true,
OldSwapchain = oldswapchain ?? new SwapchainKHR()
};
_swapchainExtension.CreateSwapchain(device.InternalHandle, swapchainCreateInfo, null, out var swapchain)
.ThrowOnError();
if (oldswapchain != null)
{
_swapchainExtension.DestroySwapchain(device.InternalHandle, oldswapchain.Value, null);
}
return swapchain;
}
private static unsafe Extent2D GetSwapchainExtent(VulkanSurface surface, SurfaceCapabilitiesKHR capabilities)
{
Extent2D swapchainExtent;
if (capabilities.CurrentExtent.Width != uint.MaxValue)
{
swapchainExtent = capabilities.CurrentExtent;
}
else
{
var surfaceSize = surface.SurfaceSize;
var width = Math.Clamp((uint)surfaceSize.Width, capabilities.MinImageExtent.Width, capabilities.MaxImageExtent.Width);
var height = Math.Clamp((uint)surfaceSize.Height, capabilities.MinImageExtent.Height, capabilities.MaxImageExtent.Height);
swapchainExtent = new Extent2D(width, height);
}
return swapchainExtent;
}
private static unsafe CompositeAlphaFlagsKHR GetSuitableCompositeAlphaFlags(SurfaceCapabilitiesKHR capabilities)
{
var compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaOpaqueBitKhr;
if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.CompositeAlphaPostMultipliedBitKhr))
{
compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaPostMultipliedBitKhr;
}
else if (capabilities.SupportedCompositeAlpha.HasFlag(CompositeAlphaFlagsKHR.CompositeAlphaPreMultipliedBitKhr))
{
compositeAlphaFlags = CompositeAlphaFlagsKHR.CompositeAlphaPreMultipliedBitKhr;
}
return compositeAlphaFlags;
}
private static unsafe PresentModeKHR GetSuitablePresentMode(VulkanPhysicalDevice physicalDevice, VulkanSurface surface, bool vsyncEnabled)
{
uint presentModesCount;
VulkanSurface.SurfaceExtension.GetPhysicalDeviceSurfacePresentModes(physicalDevice.InternalHandle,
surface.ApiHandle,
&presentModesCount, null);
var presentModes = new PresentModeKHR[presentModesCount];
fixed (PresentModeKHR* pPresentModes = presentModes)
{
VulkanSurface.SurfaceExtension.GetPhysicalDeviceSurfacePresentModes(physicalDevice.InternalHandle,
surface.ApiHandle, &presentModesCount, pPresentModes);
}
var modes = presentModes.ToList();
if (!vsyncEnabled && modes.Contains(PresentModeKHR.PresentModeImmediateKhr))
{
return PresentModeKHR.PresentModeImmediateKhr;
}
else if (modes.Contains(PresentModeKHR.PresentModeMailboxKhr))
{
return PresentModeKHR.PresentModeMailboxKhr;
}
else if (modes.Contains(PresentModeKHR.PresentModeFifoKhr))
{
return PresentModeKHR.PresentModeFifoKhr;
}
else
{
return PresentModeKHR.PresentModeImmediateKhr;
}
}
internal static VulkanDisplay CreateDisplay(VulkanInstance instance, VulkanDevice device,
VulkanPhysicalDevice physicalDevice, VulkanSurface surface)
{
var swapchain = CreateSwapchain(instance, device, physicalDevice, surface, out var extent, null, true);
return new VulkanDisplay(instance, device, physicalDevice, surface, swapchain, extent);
}
private unsafe void CreateSwapchainImages()
{
DestroyCurrentImageViews();
Size = new PixelSize((int)_swapchainExtent.Width, (int)_swapchainExtent.Height);
uint imageCount = 0;
_swapchainExtension.GetSwapchainImages(_device.InternalHandle, _swapchain, &imageCount, null);
_swapchainImages = new Image[imageCount];
fixed (Image* pSwapchainImages = _swapchainImages)
{
_swapchainExtension.GetSwapchainImages(_device.InternalHandle, _swapchain, &imageCount, pSwapchainImages);
}
_swapchainImageViews = new ImageView[imageCount];
var surfaceFormat = SurfaceFormat;
for (var i = 0; i < imageCount; i++)
{
_swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format);
}
}
private void DestroyCurrentImageViews()
{
for (var i = 0; i < _swapchainImageViews.Length; i++)
{
_instance.Api.DestroyImageView(_device.InternalHandle, _swapchainImageViews[i], Span<AllocationCallbacks>.Empty);
}
}
internal void ChangeVSyncMode(bool vsyncEnabled)
{
_vsyncStateChanged = true;
_vsyncEnabled = vsyncEnabled;
}
private void Recreate()
{
_device.WaitIdle();
_swapchain = CreateSwapchain(_instance, _device, _physicalDevice, _surface, out _swapchainExtent, _swapchain, _vsyncEnabled);
CreateSwapchainImages();
_surfaceChanged = true;
}
private unsafe ImageView CreateSwapchainImageView(Image swapchainImage, Format format)
{
var componentMapping = new ComponentMapping(
ComponentSwizzle.Identity,
ComponentSwizzle.Identity,
ComponentSwizzle.Identity,
ComponentSwizzle.Identity);
var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, 1, 0, 1);
var imageCreateInfo = new ImageViewCreateInfo
{
SType = StructureType.ImageViewCreateInfo,
Image = swapchainImage,
ViewType = ImageViewType.ImageViewType2D,
Format = format,
Components = componentMapping,
SubresourceRange = subresourceRange
};
_instance.Api.CreateImageView(_device.InternalHandle, imageCreateInfo, null, out var imageView).ThrowOnError();
return imageView;
}
public bool EnsureSwapchainAvailable()
{
if (Size != _surface.SurfaceSize || _vsyncStateChanged)
{
_vsyncStateChanged = false;
Recreate();
return false;
}
return true;
}
internal VulkanCommandBufferPool.VulkanCommandBuffer StartPresentation()
{
_nextImage = 0;
while (true)
{
var acquireResult = _swapchainExtension.AcquireNextImage(
_device.InternalHandle,
_swapchain,
ulong.MaxValue,
_semaphorePair.ImageAvailableSemaphore,
new Fence(),
ref _nextImage);
if (acquireResult == Result.ErrorOutOfDateKhr ||
acquireResult == Result.SuboptimalKhr)
{
Recreate();
}
else
{
acquireResult.ThrowOnError();
break;
}
}
var commandBuffer = CommandBufferPool.CreateCommandBuffer();
commandBuffer.BeginRecording();
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer.InternalHandle,
_swapchainImages[_nextImage], ImageLayout.Undefined,
AccessFlags.AccessNoneKhr,
ImageLayout.TransferDstOptimal,
AccessFlags.AccessTransferWriteBit,
1);
return commandBuffer;
}
internal void BlitImageToCurrentImage(VulkanSurfaceRenderTarget renderTarget, CommandBuffer commandBuffer)
{
var image = renderTarget.GetImage();
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
image.InternalHandle.Value, (ImageLayout)image.CurrentLayout,
AccessFlags.AccessNoneKhr,
ImageLayout.TransferSrcOptimal,
AccessFlags.AccessTransferReadBit,
renderTarget.MipLevels);
var srcBlitRegion = new ImageBlit
{
SrcOffsets = new ImageBlit.SrcOffsetsBuffer
{
Element0 = new Offset3D(0, 0, 0),
Element1 = new Offset3D(renderTarget.Size.Width, renderTarget.Size.Height, 1),
},
DstOffsets = new ImageBlit.DstOffsetsBuffer
{
Element0 = new Offset3D(0, 0, 0),
Element1 = new Offset3D(Size.Width, Size.Height, 1),
},
SrcSubresource = new ImageSubresourceLayers
{
AspectMask = ImageAspectFlags.ImageAspectColorBit,
BaseArrayLayer = 0,
LayerCount = 1,
MipLevel = 0
},
DstSubresource = new ImageSubresourceLayers
{
AspectMask = ImageAspectFlags.ImageAspectColorBit,
BaseArrayLayer = 0,
LayerCount = 1,
MipLevel = 0
}
};
_device.Api.CmdBlitImage(commandBuffer, image.InternalHandle.Value,
ImageLayout.TransferSrcOptimal,
_swapchainImages[_nextImage],
ImageLayout.TransferDstOptimal,
1,
srcBlitRegion,
Filter.Linear);
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer,
image.InternalHandle.Value, ImageLayout.TransferSrcOptimal,
AccessFlags.AccessTransferReadBit,
(ImageLayout)image.CurrentLayout,
AccessFlags.AccessNoneKhr,
renderTarget.MipLevels);
}
internal unsafe void EndPresentation(VulkanCommandBufferPool.VulkanCommandBuffer commandBuffer)
{
VulkanMemoryHelper.TransitionLayout(_device, commandBuffer.InternalHandle,
_swapchainImages[_nextImage], ImageLayout.TransferDstOptimal,
AccessFlags.AccessNoneKhr,
ImageLayout.PresentSrcKhr,
AccessFlags.AccessNoneKhr,
1);
commandBuffer.Submit(
stackalloc[] { _semaphorePair.ImageAvailableSemaphore },
stackalloc[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit },
stackalloc[] { _semaphorePair.RenderFinishedSemaphore });
var semaphore = _semaphorePair.RenderFinishedSemaphore;
var swapchain = _swapchain;
var nextImage = _nextImage;
Result result;
var presentInfo = new PresentInfoKHR
{
SType = StructureType.PresentInfoKhr,
WaitSemaphoreCount = 1,
PWaitSemaphores = &semaphore,
SwapchainCount = 1,
PSwapchains = &swapchain,
PImageIndices = &nextImage,
PResults = &result
};
lock (_device.Lock)
{
_swapchainExtension.QueuePresent(_device.PresentQueue.InternalHandle, presentInfo);
}
CommandBufferPool.FreeUsedCommandBuffers();
Presented?.Invoke(this, null);
}
}
}