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:
Emmanuel Hansen
2022-08-16 16:32:37 +00:00
committed by GitHub
parent 0ec933a615
commit c8f9292bab
28 changed files with 585 additions and 312 deletions

View File

@ -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();
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++)
{
var imageCreateInfo = new ImageCreateInfo
{
SType = StructureType.ImageCreateInfo,
ImageType = ImageType.ImageType2D,
Format = _format,
Extent =
new Extent3D((uint?)_width,
(uint?)_height, 1),
MipLevels = 1,
ArrayLayers = 1,
Samples = SampleCountFlags.SampleCount1Bit,
Tiling = ImageTiling.Optimal,
Usage = ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageTransferDstBit,
SharingMode = SharingMode.Exclusive,
InitialLayout = ImageLayout.Undefined,
Flags = ImageCreateFlags.ImageCreateMutableFormatBit
};
_gd.Api.CreateImage(_device, imageCreateInfo, null, out var image).ThrowOnError();
_images[i] = new Auto<DisposableImage>(new DisposableImage(_gd.Api, _device, image));
_gd.Api.GetImageMemoryRequirements(_device, image,
out var memoryRequirements);
var allocation = _gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, memoryRequirements, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit);
_imageSizes[i] = allocation.Size;
@ -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();
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(
@ -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; }
private readonly Auto<DisposableImage> _image;
private readonly Auto<MemoryAllocation> _memory;
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;
this.Memory = memory;
this.MemorySize = memorySize;
this.MemoryOffset = memoryOffset;
this.ReadySemaphore = readySemaphore;
this.AvailableSemaphore = availableSemaphore;
_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()
{
_memory.DecrementReferenceCount();
_image.DecrementReferenceCount();
}
}
}

View File

@ -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)

View File

@ -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);
}
}