Fix Vulkan validation errors (#92)

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 <guillerman0000@gmail.com>
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/92
This commit is contained in:
AsperTheDog 2026-05-15 15:36:14 +00:00 committed by sh0inx
parent 48888bd014
commit 58bd19a2f3
7 changed files with 84 additions and 19 deletions

View file

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

View file

@ -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);
ImageBlit region = new()
{
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));
Extent3D extent = new((uint)copyWidth, (uint)copyHeight, (uint)srcDepth);
if (srcInfo.Samples > 1 && srcInfo.Samples != dstInfo.Samples)

View file

@ -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
SampleCountFlags sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
ImageUsageFlags usage = GetImageUsage(info.Format, gd.Capabilities, isMsImageStorageSupported, true);
ImageUsageFlags usage = GetImageUsage(info.Format, gd.Capabilities, isMsImageStorageSupported);
UsageFlags = usage;
ImageCreateFlags flags = ImageCreateFlags.CreateMutableFormatBit | ImageCreateFlags.CreateExtendedUsageBit;
@ -159,7 +162,7 @@ namespace Ryujinx.Graphics.Vulkan
_imageAuto = new Auto<DisposableImage>(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, in HardwareCapabilities capabilities, bool isMsImageStorageSupported, bool extendedUsage)
public static ImageUsageFlags GetImageUsage(Format format, in HardwareCapabilities capabilities, bool isMsImageStorageSupported)
{
ImageUsageFlags usage = DefaultUsageFlags;
@ -320,7 +323,7 @@ namespace Ryujinx.Graphics.Vulkan
usage |= ImageUsageFlags.ColorAttachmentBit;
}
if ((format.IsImageCompatible && isMsImageStorageSupported) || extendedUsage)
if (format.IsImageCompatible && isMsImageStorageSupported)
{
usage |= ImageUsageFlags.StorageBit;
}

View file

@ -64,7 +64,7 @@ namespace Ryujinx.Graphics.Vulkan
bool isMsImageStorageSupported = gd.Capabilities.SupportsShaderStorageImageMultisample || !info.Target.IsMultisample;
VkFormat format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format, isMsImageStorageSupported);
ImageUsageFlags usage = TextureStorage.GetImageUsage(info.Format, gd.Capabilities, isMsImageStorageSupported, false);
ImageUsageFlags usage = TextureStorage.GetImageUsage(info.Format, gd.Capabilities, isMsImageStorageSupported) & storage.UsageFlags;
uint levels = (uint)info.Levels;
uint layers = (uint)info.GetLayers();
@ -133,6 +133,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.
@ -257,6 +259,8 @@ namespace Ryujinx.Graphics.Vulkan
dstImage,
src.Info,
dst.Info,
src.Storage.Info,
dst.Storage.Info,
src.FirstLayer,
dst.FirstLayer,
src.FirstLevel,
@ -310,6 +314,8 @@ namespace Ryujinx.Graphics.Vulkan
dstImage,
src.Info,
dst.Info,
src.Storage.Info,
dst.Storage.Info,
src.FirstLayer,
dst.FirstLayer,
src.FirstLevel,
@ -385,6 +391,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,
@ -410,6 +418,8 @@ namespace Ryujinx.Graphics.Vulkan
dst.GetImage().Get(cbs).Value,
src.Info,
dst.Info,
src.Storage.Info,
dst.Storage.Info,
srcRegion,
dstRegion,
src.FirstLayer,
@ -463,6 +473,8 @@ namespace Ryujinx.Graphics.Vulkan
dstImage.Get(cbs).Value,
src.Info,
dst.Info,
src.Storage.Info,
dst.Storage.Info,
srcRegion,
dstRegion,
src.FirstLayer,

View file

@ -68,9 +68,7 @@ namespace Ryujinx.Graphics.Vulkan
int stride = (_stride + (alignment - 1)) & -alignment;
int newSize = (_size / _stride) * stride;
Buffer 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;
@ -93,11 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
if (autoBuffer != null)
{
int offset = _offset;
bool mirrorable = _size <= VertexBufferMaxMirrorable;
Buffer 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);
}
}

View file

@ -15,6 +15,10 @@ namespace Ryujinx.Graphics.Vulkan
private readonly NativeArray<ulong> _sizes;
private readonly NativeArray<ulong> _strides;
private readonly Auto<DisposableBuffer>[] _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<ulong>(Constants.MaxVertexBuffers);
_sizes = new NativeArray<ulong>(Constants.MaxVertexBuffers);
_strides = new NativeArray<ulong>(Constants.MaxVertexBuffers);
_bufferAutos = new Auto<DisposableBuffer>[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<DisposableBuffer> 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(

View file

@ -391,12 +391,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();
}
@ -455,6 +455,8 @@ namespace Ryujinx.Graphics.Vulkan
ImageLayout.General,
ImageLayout.PresentSrcKhr);
_gd.FlushAllCommands();
_gd.CommandBufferPool.Return(
cbs,
[_imageAvailableSemaphores[semaphoreIndex]],