From c0078088ddc1df9720de05f2501cf794c4cbdf30 Mon Sep 17 00:00:00 2001 From: AsperTheDog Date: Thu, 14 May 2026 08:59:10 +0000 Subject: [PATCH] Add shader non-uniform indexing support (#91) This PR marks ALL texture indexes as nonuniform to fix an issue with the paths in Tomodachi Life: Living the Dream on AMD cards. It should have a negligible impact on performance (and it should not have an impact at all on NVIDIA cards!) It's caused by what is called 'implicit non-uniform sampler array indexing'. The idea is basically that some GPUs optimize texture lookups from indexed texture arrays, by assuming that you are never going to index different textures within a single workgroup. What this causes is that visual glitch where a subgroup is tasked with rendering a block of the screen, and in the boundaries some cores are indexing the wrong texture. Co-authored-by: AsperTheDog Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/91 --- src/Ryujinx.Graphics.GAL/Capabilities.cs | 3 ++ .../Shader/DiskCache/DiskCacheHostStorage.cs | 2 +- .../Shader/GpuAccessorBase.cs | 2 + src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 1 + .../CodeGen/Spirv/Instructions.cs | 50 +++++++++++++++++-- .../CodeGen/Spirv/SpirvGenerator.cs | 8 +++ src/Ryujinx.Graphics.Shader/IGpuAccessor.cs | 4 ++ .../Translation/HostCapabilities.cs | 3 ++ .../Translation/TranslatorContext.cs | 1 + .../VulkanInitialization.cs | 2 + src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 3 ++ 11 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index 1eec80e51..4271c3d18 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -42,6 +42,7 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsShaderBallot; public readonly bool SupportsShaderBarrierDivergence; public readonly bool SupportsShaderFloat64; + public readonly bool SupportsShaderNonUniformIndexing; public readonly bool SupportsTextureGatherOffsets; public readonly bool SupportsTextureShadowLod; public readonly bool SupportsVertexStoreAndAtomics; @@ -110,6 +111,7 @@ namespace Ryujinx.Graphics.GAL bool supportsShaderBallot, bool supportsShaderBarrierDivergence, bool supportsShaderFloat64, + bool supportsShaderNonUniformIndexing, bool supportsTextureGatherOffsets, bool supportsTextureShadowLod, bool supportsVertexStoreAndAtomics, @@ -172,6 +174,7 @@ namespace Ryujinx.Graphics.GAL SupportsShaderBallot = supportsShaderBallot; SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; SupportsShaderFloat64 = supportsShaderFloat64; + SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing; SupportsTextureGatherOffsets = supportsTextureGatherOffsets; SupportsTextureShadowLod = supportsTextureShadowLod; SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index 6444b18e3..7b58e18c3 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const ushort FileFormatVersionMajor = 1; private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 7353; + private const uint CodeGenVersion = 7354; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index d89eebabf..2f8c329e5 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -231,6 +231,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public bool QueryHostSupportsShaderFloat64() => _context.Capabilities.SupportsShaderFloat64; + public bool QueryHostSupportsShaderNonUniformIndexing() => _context.Capabilities.SupportsShaderNonUniformIndexing; + public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat; public bool QueryHostSupportsTextureGatherOffsets() => _context.Capabilities.SupportsTextureGatherOffsets; diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index af1494bbe..acc0dbd68 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -184,6 +184,7 @@ namespace Ryujinx.Graphics.OpenGL supportsShaderBallot: HwCapabilities.SupportsShaderBallot, supportsShaderBarrierDivergence: !(intelWindows || intelUnix), supportsShaderFloat64: true, + supportsShaderNonUniformIndexing: false, supportsTextureGatherOffsets: true, supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod, supportsVertexStoreAndAtomics: true, diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs index 83b037c1c..9de806d89 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs @@ -587,6 +587,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv return OperationResult.Invalid; } + private static void MarkNonUniform(CodeGenContext context, SpvInstruction inst) + { + if (context.HostCapabilities.SupportsShaderNonUniformIndexing) + { + context.Decorate(inst, Decoration.NonUniform); + } + } + private static OperationResult GenerateImageAtomic(CodeGenContext context, AstOperation operation) { AstTextureOperation texOp = (AstTextureOperation)operation; @@ -613,6 +621,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv SpvInstruction textureIndex = Src(AggregateType.S32); image = context.AccessChain(imagePointerType, image, textureIndex); + MarkNonUniform(context, image); } int coordsCount = texOp.Type.Dimensions; @@ -683,15 +692,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()]; SpvInstruction image = declaration.Image; + bool isIndexed = declaration.IsIndexed; - if (declaration.IsIndexed) + if (isIndexed) { SpvInstruction textureIndex = Src(AggregateType.S32); image = context.AccessChain(declaration.ImagePointerType, image, textureIndex); + MarkNonUniform(context, image); } image = context.Load(declaration.ImageType, image); + if (isIndexed) + { + MarkNonUniform(context, image); + } int coordsCount = texOp.Type.Dimensions; @@ -740,15 +755,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv ImageDeclaration declaration = context.Images[texOp.GetTextureSetAndBinding()]; SpvInstruction image = declaration.Image; + bool isIndexed = declaration.IsIndexed; - if (declaration.IsIndexed) + if (isIndexed) { SpvInstruction textureIndex = Src(AggregateType.S32); image = context.AccessChain(declaration.ImagePointerType, image, textureIndex); + MarkNonUniform(context, image); } image = context.Load(declaration.ImageType, image); + if (isIndexed) + { + MarkNonUniform(context, image); + } int coordsCount = texOp.Type.Dimensions; @@ -1878,35 +1899,56 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv private static SpvInstruction GenerateSampledImageLoad(CodeGenContext context, AstTextureOperation texOp, SamplerDeclaration declaration, ref int srcIndex) { SpvInstruction image = declaration.Image; + bool imageIndexed = declaration.IsIndexed; - if (declaration.IsIndexed) + if (imageIndexed) { SpvInstruction textureIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++)); image = context.AccessChain(declaration.SampledImagePointerType, image, textureIndex); + MarkNonUniform(context, image); } if (texOp.IsSeparate) { image = context.Load(declaration.ImageType, image); + if (imageIndexed) + { + MarkNonUniform(context, image); + } SamplerDeclaration samplerDeclaration = context.Samplers[texOp.GetSamplerSetAndBinding()]; SpvInstruction sampler = samplerDeclaration.Image; + bool samplerIndexed = samplerDeclaration.IsIndexed; - if (samplerDeclaration.IsIndexed) + if (samplerIndexed) { SpvInstruction samplerIndex = context.Get(AggregateType.S32, texOp.GetSource(srcIndex++)); sampler = context.AccessChain(samplerDeclaration.SampledImagePointerType, sampler, samplerIndex); + MarkNonUniform(context, sampler); } sampler = context.Load(samplerDeclaration.ImageType, sampler); + if (samplerIndexed) + { + MarkNonUniform(context, sampler); + } + image = context.SampledImage(declaration.SampledImageType, image, sampler); + if (imageIndexed || samplerIndexed) + { + MarkNonUniform(context, image); + } } else { image = context.Load(declaration.SampledImageType, image); + if (imageIndexed) + { + MarkNonUniform(context, image); + } } return image; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs index e1561446b..2dd7186ba 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs @@ -60,6 +60,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv context.AddCapability(Capability.Float64); } + if (parameters.HostCapabilities.SupportsShaderNonUniformIndexing) + { + context.AddExtension("SPV_EXT_descriptor_indexing"); + context.AddCapability(Capability.ShaderNonUniform); + context.AddCapability(Capability.SampledImageArrayNonUniformIndexing); + context.AddCapability(Capability.StorageImageArrayNonUniformIndexing); + } + if (parameters.Definitions.TransformFeedbackEnabled && parameters.Definitions.LastInVertexPipeline) { context.AddCapability(Capability.TransformFeedback); diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index 4e6d6edf9..77953df05 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -336,6 +336,10 @@ namespace Ryujinx.Graphics.Shader { return true; } + bool QueryHostSupportsShaderNonUniformIndexing() + { + return false; + } /// /// Queries host GPU support for signed normalized buffer texture formats. diff --git a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs index 11fe6599d..bfd85c158 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs @@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Shader.Translation public readonly bool SupportsShaderBallot; public readonly bool SupportsShaderBarrierDivergence; public readonly bool SupportsShaderFloat64; + public readonly bool SupportsShaderNonUniformIndexing; public readonly bool SupportsTextureShadowLod; public readonly bool SupportsViewportMask; @@ -20,6 +21,7 @@ namespace Ryujinx.Graphics.Shader.Translation bool supportsShaderBallot, bool supportsShaderBarrierDivergence, bool supportsShaderFloat64, + bool supportsShaderNonUniformIndexing, bool supportsTextureShadowLod, bool supportsViewportMask) { @@ -30,6 +32,7 @@ namespace Ryujinx.Graphics.Shader.Translation SupportsShaderBallot = supportsShaderBallot; SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; SupportsShaderFloat64 = supportsShaderFloat64; + SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing; SupportsTextureShadowLod = supportsTextureShadowLod; SupportsViewportMask = supportsViewportMask; } diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs index e1ca22610..c7c562947 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -364,6 +364,7 @@ namespace Ryujinx.Graphics.Shader.Translation GpuAccessor.QueryHostSupportsShaderBallot(), GpuAccessor.QueryHostSupportsShaderBarrierDivergence(), GpuAccessor.QueryHostSupportsShaderFloat64(), + GpuAccessor.QueryHostSupportsShaderNonUniformIndexing(), GpuAccessor.QueryHostSupportsTextureShadowLod(), GpuAccessor.QueryHostSupportsViewportMask()); diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 02c4e6873..9cd8f90d7 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -494,6 +494,8 @@ namespace Ryujinx.Graphics.Vulkan UniformBufferStandardLayout = supportedPhysicalDeviceVulkan12Features.UniformBufferStandardLayout, UniformAndStorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.UniformAndStorageBuffer8BitAccess, StorageBuffer8BitAccess = supportedPhysicalDeviceVulkan12Features.StorageBuffer8BitAccess, + ShaderSampledImageArrayNonUniformIndexing = supportedPhysicalDeviceVulkan12Features.ShaderSampledImageArrayNonUniformIndexing, + ShaderStorageImageArrayNonUniformIndexing = supportedPhysicalDeviceVulkan12Features.ShaderStorageImageArrayNonUniformIndexing, }; pExtendedFeatures = &featuresVk12; diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 5f1c50b00..ccb541a34 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -775,6 +775,9 @@ namespace Ryujinx.Graphics.Vulkan supportsShaderBallot: false, supportsShaderBarrierDivergence: Vendor != Vendor.Intel, supportsShaderFloat64: Capabilities.SupportsShaderFloat64, + supportsShaderNonUniformIndexing: + featuresVk12.ShaderSampledImageArrayNonUniformIndexing && + featuresVk12.ShaderStorageImageArrayNonUniformIndexing, supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended && !IsMoltenVk, supportsTextureShadowLod: false, supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,