From e88fd3273d4f525dfd689461c58bd774d411d908 Mon Sep 17 00:00:00 2001 From: AsperTheDog Date: Fri, 15 May 2026 10:36:14 -0500 Subject: [PATCH] Fix Vulkan validation errors This PR fixes several validation errors caused by invalid Vulkan usage. These validation errors often end up invoking Undefined Behavior on the driver side, which can lead to artifacts or crashes which are driver specific and otherwise incredibly hard to track. I don't think it should have any impact on performance, but it would be good to test it with as many games as possible (maybe a bug in a game was fixed?). Each commit fixes an error. I added to each a description with the validation error that was fixed and a small explanation on what was causing it and how I fixed it. Co-authored-by: AsperTheDog --- src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs | 9 +++++- src/Ryujinx.Graphics.Vulkan/TextureCopy.cs | 31 +++++++++++++++++++ src/Ryujinx.Graphics.Vulkan/TextureStorage.cs | 11 ++++--- src/Ryujinx.Graphics.Vulkan/TextureView.cs | 15 ++++++++- .../VertexBufferState.cs | 10 ++---- .../VertexBufferUpdater.cs | 24 +++++++++++--- src/Ryujinx.Graphics.Vulkan/Window.cs | 4 ++- 7 files changed, 84 insertions(+), 20 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs index e744dc0b4..2940fad6c 100644 --- a/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs +++ b/src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs @@ -46,7 +46,14 @@ namespace Ryujinx.Graphics.Vulkan public static (AccessFlags Access, PipelineStageFlags Stages) GetSubpassAccessSuperset(VulkanRenderer gd) { - AccessFlags access = BufferAccess; + AccessFlags access = BufferAccess | + AccessFlags.ShaderReadBit | + AccessFlags.ShaderWriteBit | + AccessFlags.ColorAttachmentReadBit | + AccessFlags.ColorAttachmentWriteBit | + AccessFlags.DepthStencilAttachmentReadBit | + AccessFlags.DepthStencilAttachmentWriteBit; + PipelineStageFlags stages = PipelineStageFlags.AllGraphicsBit; if (gd.TransformFeedbackApi != null) diff --git a/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs b/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs index 45cddd772..fce3726a8 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureCopy.cs @@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Vulkan Image dstImage, TextureCreateInfo srcInfo, TextureCreateInfo dstInfo, + TextureCreateInfo srcStorageInfo, + TextureCreateInfo dstStorageInfo, Extents2D srcRegion, Extents2D dstRegion, int srcLayer, @@ -40,6 +42,13 @@ namespace Ryujinx.Graphics.Vulkan return (xy1, xy2); } + static (Offset3D, Offset3D) ClampOffsetsToMip(Offset3D xy1, Offset3D xy2, int mipW, int mipH) + { + return ( + new Offset3D(Math.Min(xy1.X, mipW), Math.Min(xy1.Y, mipH), xy1.Z), + new Offset3D(Math.Min(xy2.X, mipW), Math.Min(xy2.Y, mipH), xy2.Z)); + } + if (srcAspectFlags == 0) { srcAspectFlags = srcInfo.Format.ConvertAspectFlags(); @@ -80,6 +89,14 @@ namespace Ryujinx.Graphics.Vulkan (srcOffsets.Element0, srcOffsets.Element1) = ExtentsToOffset3D(srcRegion, srcInfo.Width, srcInfo.Height, level); (dstOffsets.Element0, dstOffsets.Element1) = ExtentsToOffset3D(dstRegion, dstInfo.Width, dstInfo.Height, level); + int srcMipW = Math.Max(1, srcStorageInfo.Width >> (int)copySrcLevel); + int srcMipH = Math.Max(1, srcStorageInfo.Height >> (int)copySrcLevel); + int dstMipW = Math.Max(1, dstStorageInfo.Width >> (int)copyDstLevel); + int dstMipH = Math.Max(1, dstStorageInfo.Height >> (int)copyDstLevel); + + (srcOffsets.Element0, srcOffsets.Element1) = ClampOffsetsToMip(srcOffsets.Element0, srcOffsets.Element1, srcMipW, srcMipH); + (dstOffsets.Element0, dstOffsets.Element1) = ClampOffsetsToMip(dstOffsets.Element0, dstOffsets.Element1, dstMipW, dstMipH); + var region = new ImageBlit { SrcSubresource = srcSl, @@ -121,6 +138,8 @@ namespace Ryujinx.Graphics.Vulkan Image dstImage, TextureCreateInfo srcInfo, TextureCreateInfo dstInfo, + TextureCreateInfo srcStorageInfo, + TextureCreateInfo dstStorageInfo, int srcViewLayer, int dstViewLayer, int srcViewLevel, @@ -151,6 +170,8 @@ namespace Ryujinx.Graphics.Vulkan dstImage, srcInfo, dstInfo, + srcStorageInfo, + dstStorageInfo, srcViewLayer, dstViewLayer, srcViewLevel, @@ -186,6 +207,8 @@ namespace Ryujinx.Graphics.Vulkan Image dstImage, TextureCreateInfo srcInfo, TextureCreateInfo dstInfo, + TextureCreateInfo srcStorageInfo, + TextureCreateInfo dstStorageInfo, int srcViewLayer, int dstViewLayer, int srcViewLevel, @@ -314,6 +337,14 @@ namespace Ryujinx.Graphics.Vulkan int copyWidth = sizeInBlocks ? BitUtils.DivRoundUp(width, blockWidth) : width; int copyHeight = sizeInBlocks ? BitUtils.DivRoundUp(height, blockHeight) : height; + int srcMipW = Math.Max(1, srcStorageInfo.Width >> (srcViewLevel + srcLevel + level)); + int srcMipH = Math.Max(1, srcStorageInfo.Height >> (srcViewLevel + srcLevel + level)); + int dstMipW = Math.Max(1, dstStorageInfo.Width >> (dstViewLevel + dstLevel + level)); + int dstMipH = Math.Max(1, dstStorageInfo.Height >> (dstViewLevel + dstLevel + level)); + + copyWidth = Math.Min(copyWidth, Math.Min(srcMipW, dstMipW)); + copyHeight = Math.Min(copyHeight, Math.Min(srcMipH, dstMipH)); + var extent = new Extent3D((uint)copyWidth, (uint)copyHeight, (uint)srcDepth); if (srcInfo.Samples > 1 && srcInfo.Samples != dstInfo.Samples) diff --git a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs index aa30399d6..7c04336d3 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureStorage.cs @@ -67,6 +67,8 @@ namespace Ryujinx.Graphics.Vulkan public VkFormat VkFormat { get; } + public ImageUsageFlags UsageFlags { get; } + public unsafe TextureStorage( VulkanRenderer gd, Device device, @@ -93,7 +95,8 @@ namespace Ryujinx.Graphics.Vulkan var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples); - var usage = GetImageUsage(info.Format, info.Target, gd.Capabilities); + ImageUsageFlags usage = GetImageUsage(info.Format, info.Target, gd.Capabilities, isMsImageStorageSupported); + UsageFlags = usage; var flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit; @@ -159,7 +162,7 @@ namespace Ryujinx.Graphics.Vulkan _imageAuto = new Auto(new DisposableImage(_gd.Api, device, _image)); - InitialTransition(ImageLayout.Preinitialized, ImageLayout.General); + InitialTransition(ImageLayout.Undefined, ImageLayout.General); } _slices = new TextureSliceInfo[levels * _depthOrLayers]; @@ -307,7 +310,7 @@ namespace Ryujinx.Graphics.Vulkan } } - public static ImageUsageFlags GetImageUsage(Format format, Target target, in HardwareCapabilities capabilities) + public static ImageUsageFlags GetImageUsage(Format format, Target target, in HardwareCapabilities capabilities, bool isMsImageStorageSupported) { var usage = DefaultUsageFlags; @@ -320,8 +323,6 @@ namespace Ryujinx.Graphics.Vulkan usage |= ImageUsageFlags.ColorAttachmentBit; } - bool isMsImageStorageSupported = capabilities.SupportsShaderStorageImageMultisample; - if (format.IsImageCompatible() && (isMsImageStorageSupported || !target.IsMultisample())) { usage |= ImageUsageFlags.StorageBit; diff --git a/src/Ryujinx.Graphics.Vulkan/TextureView.cs b/src/Ryujinx.Graphics.Vulkan/TextureView.cs index 924b3babf..d210fc3d0 100644 --- a/src/Ryujinx.Graphics.Vulkan/TextureView.cs +++ b/src/Ryujinx.Graphics.Vulkan/TextureView.cs @@ -64,7 +64,8 @@ namespace Ryujinx.Graphics.Vulkan bool isMsImageStorageSupported = gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample(); var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format, isMsImageStorageSupported); - var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities); + var usage = TextureStorage.GetImageUsage(info.Format, info.Target, gd.Capabilities, isMsImageStorageSupported) & storage.UsageFlags; + var levels = (uint)info.Levels; var layers = (uint)info.GetLayers(); @@ -130,6 +131,8 @@ namespace Ryujinx.Graphics.Vulkan shaderUsage |= ImageUsageFlags.StorageBit; } + shaderUsage &= storage.UsageFlags; + _imageView = CreateImageView(componentMapping, subresourceRange, type, shaderUsage); // Framebuffer attachments and storage images requires a identity component mapping. @@ -254,6 +257,8 @@ namespace Ryujinx.Graphics.Vulkan dstImage, src.Info, dst.Info, + src.Storage.Info, + dst.Storage.Info, src.FirstLayer, dst.FirstLayer, src.FirstLevel, @@ -307,6 +312,8 @@ namespace Ryujinx.Graphics.Vulkan dstImage, src.Info, dst.Info, + src.Storage.Info, + dst.Storage.Info, src.FirstLayer, dst.FirstLayer, src.FirstLevel, @@ -382,6 +389,8 @@ namespace Ryujinx.Graphics.Vulkan dst.GetImage().Get(cbs).Value, src.Info, dst.Info, + src.Storage.Info, + dst.Storage.Info, src.FirstLayer, dst.FirstLayer, src.FirstLevel, @@ -407,6 +416,8 @@ namespace Ryujinx.Graphics.Vulkan dst.GetImage().Get(cbs).Value, src.Info, dst.Info, + src.Storage.Info, + dst.Storage.Info, srcRegion, dstRegion, src.FirstLayer, @@ -460,6 +471,8 @@ namespace Ryujinx.Graphics.Vulkan dstImage.Get(cbs).Value, src.Info, dst.Info, + src.Storage.Info, + dst.Storage.Info, srcRegion, dstRegion, src.FirstLayer, diff --git a/src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs b/src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs index f4d512f92..c28fe5cd5 100644 --- a/src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs +++ b/src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs @@ -67,9 +67,7 @@ namespace Ryujinx.Graphics.Vulkan int stride = (_stride + (alignment - 1)) & -alignment; int newSize = (_size / _stride) * stride; - var buffer = autoBuffer.Get(cbs, 0, newSize).Value; - - updater.BindVertexBuffer(cbs, binding, buffer, 0, (ulong)newSize, (ulong)stride); + updater.BindVertexBuffer(cbs, binding, autoBuffer, 0, newSize, (ulong)stride); _buffer = autoBuffer; @@ -92,11 +90,7 @@ namespace Ryujinx.Graphics.Vulkan if (autoBuffer != null) { - int offset = _offset; - bool mirrorable = _size <= VertexBufferMaxMirrorable; - var buffer = mirrorable ? autoBuffer.GetMirrorable(cbs, ref offset, _size, out _).Value : autoBuffer.Get(cbs, offset, _size).Value; - - updater.BindVertexBuffer(cbs, binding, buffer, (ulong)offset, (ulong)_size, (ulong)_stride); + updater.BindVertexBuffer(cbs, binding, autoBuffer, _offset, _size, (ulong)_stride); } } diff --git a/src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs b/src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs index 94269dd76..8927d2264 100644 --- a/src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs @@ -15,6 +15,10 @@ namespace Ryujinx.Graphics.Vulkan private readonly NativeArray _sizes; private readonly NativeArray _strides; + private readonly Auto[] _bufferAutos; + private readonly int[] _bufferOffsetsForGet; + private readonly int[] _bufferSizesForGet; + public VertexBufferUpdater(VulkanRenderer gd) { _gd = gd; @@ -23,9 +27,13 @@ namespace Ryujinx.Graphics.Vulkan _offsets = new NativeArray(Constants.MaxVertexBuffers); _sizes = new NativeArray(Constants.MaxVertexBuffers); _strides = new NativeArray(Constants.MaxVertexBuffers); + + _bufferAutos = new Auto[Constants.MaxVertexBuffers]; + _bufferOffsetsForGet = new int[Constants.MaxVertexBuffers]; + _bufferSizesForGet = new int[Constants.MaxVertexBuffers]; } - public void BindVertexBuffer(CommandBufferScoped cbs, uint binding, VkBuffer buffer, ulong offset, ulong size, ulong stride) + public void BindVertexBuffer(CommandBufferScoped cbs, uint binding, Auto autoBuffer, int offset, int size, ulong stride) { if (_count == 0) { @@ -39,9 +47,11 @@ namespace Ryujinx.Graphics.Vulkan int index = (int)_count; - _buffers[index] = buffer; - _offsets[index] = offset; - _sizes[index] = size; + _bufferAutos[index] = autoBuffer; + _bufferOffsetsForGet[index] = offset; + _bufferSizesForGet[index] = size; + _offsets[index] = (ulong)offset; + _sizes[index] = (ulong)size; _strides[index] = stride; _count++; @@ -51,6 +61,12 @@ namespace Ryujinx.Graphics.Vulkan { if (_count != 0) { + for (int i = 0; i < _count; i++) + { + _buffers[i] = _bufferAutos[i].Get(cbs, _bufferOffsetsForGet[i], _bufferSizesForGet[i]).Value; + _bufferAutos[i] = null; + } + if (_gd.Capabilities.SupportsExtendedDynamicState) { _gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2( diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs index bfa15d9a5..7c9f9ad5b 100644 --- a/src/Ryujinx.Graphics.Vulkan/Window.cs +++ b/src/Ryujinx.Graphics.Vulkan/Window.cs @@ -390,12 +390,12 @@ namespace Ryujinx.Graphics.Vulkan { if (_effect != null) { + _gd.FlushAllCommands(); _gd.CommandBufferPool.Return( cbs, null, [PipelineStageFlags.ColorAttachmentOutputBit], null); - _gd.FlushAllCommands(); cbs.GetFence().Wait(); cbs = _gd.CommandBufferPool.Rent(); } @@ -454,6 +454,8 @@ namespace Ryujinx.Graphics.Vulkan ImageLayout.General, ImageLayout.PresentSrcKhr); + _gd.FlushAllCommands(); + _gd.CommandBufferPool.Return( cbs, [_imageAvailableSemaphores[semaphoreIndex]],