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(); 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.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.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); } } }