diff --git a/src/Ryujinx.Graphics.GAL/Capabilities.cs b/src/Ryujinx.Graphics.GAL/Capabilities.cs index 8959bf93e..8658d7344 100644 --- a/src/Ryujinx.Graphics.GAL/Capabilities.cs +++ b/src/Ryujinx.Graphics.GAL/Capabilities.cs @@ -8,6 +8,7 @@ namespace Ryujinx.Graphics.GAL public readonly string VendorName; public readonly bool HasFrontFacingBug; + public readonly bool HasUnsizedDescriptorArrayBug; public readonly bool HasVectorIndexingBug; public readonly bool NeedsFragmentOutputSpecialization; public readonly bool ReduceShaderPrecision; @@ -24,6 +25,7 @@ namespace Ryujinx.Graphics.GAL public readonly bool SupportsScaledVertexFormats; public readonly bool SupportsSnormBufferTextureFormat; public readonly bool Supports5BitComponentFormat; + public readonly bool SupportsBindlessTextures; public readonly bool SupportsBlendEquationAdvanced; public readonly bool SupportsFragmentShaderInterlock; public readonly bool SupportsFragmentShaderOrderingIntel; @@ -64,6 +66,7 @@ namespace Ryujinx.Graphics.GAL TargetApi api, string vendorName, bool hasFrontFacingBug, + bool hasUnsizedDescriptorArrayBug, bool hasVectorIndexingBug, bool needsFragmentOutputSpecialization, bool reduceShaderPrecision, @@ -79,6 +82,7 @@ namespace Ryujinx.Graphics.GAL bool supportsScaledVertexFormats, bool supportsSnormBufferTextureFormat, bool supports5BitComponentFormat, + bool supportsBindlessTextures, bool supportsBlendEquationAdvanced, bool supportsFragmentShaderInterlock, bool supportsFragmentShaderOrderingIntel, @@ -115,6 +119,7 @@ namespace Ryujinx.Graphics.GAL Api = api; VendorName = vendorName; HasFrontFacingBug = hasFrontFacingBug; + HasUnsizedDescriptorArrayBug = hasUnsizedDescriptorArrayBug; HasVectorIndexingBug = hasVectorIndexingBug; NeedsFragmentOutputSpecialization = needsFragmentOutputSpecialization; ReduceShaderPrecision = reduceShaderPrecision; @@ -130,6 +135,7 @@ namespace Ryujinx.Graphics.GAL SupportsScaledVertexFormats = supportsScaledVertexFormats; SupportsSnormBufferTextureFormat = supportsSnormBufferTextureFormat; Supports5BitComponentFormat = supports5BitComponentFormat; + SupportsBindlessTextures = supportsBindlessTextures; SupportsBlendEquationAdvanced = supportsBlendEquationAdvanced; SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel; diff --git a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index 8deb83c74..b1184e919 100644 --- a/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -157,6 +157,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug; + public bool QueryHostHasUnsizedDescriptorArrayBug() => _context.Capabilities.HasUnsizedDescriptorArrayBug; + public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug; public int QueryHostStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment; @@ -165,6 +167,8 @@ namespace Ryujinx.Graphics.Gpu.Shader public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat; + public bool QueryHostSupportsBindlessTextures() => _context.Capabilities.SupportsBindlessTextures; + public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock; public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel; diff --git a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs index 6c97274c0..f15d3b12b 100644 --- a/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs +++ b/src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs @@ -134,6 +134,7 @@ namespace Ryujinx.Graphics.OpenGL api: TargetApi.OpenGL, vendorName: GpuVendor, hasFrontFacingBug: intelWindows, + hasUnsizedDescriptorArrayBug: false, hasVectorIndexingBug: amdWindows, needsFragmentOutputSpecialization: false, reduceShaderPrecision: false, @@ -148,6 +149,7 @@ namespace Ryujinx.Graphics.OpenGL supportsR4G4B4A4Format: true, supportsSnormBufferTextureFormat: false, supports5BitComponentFormat: true, + supportsBindlessTextures: HwCapabilities.SupportsArbBindlessTexture, supportsBlendEquationAdvanced: HwCapabilities.SupportsBlendEquationAdvanced, supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock, supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering, diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs index 85bd8ab48..421a35143 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs @@ -190,7 +190,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (sampler.ArraySize == 0) { - var sampledImageArrayType = context.TypeRuntimeArray(imageTypeForSampler); + var sampledImageArrayType = context.HostCapabilities.HasUnsizedDescriptorArrayBug + ? context.TypeArray(imageTypeForSampler, context.Constant(context.TypeU32(), 1)) + : context.TypeRuntimeArray(imageTypeForSampler); sampledImageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, sampledImageArrayType); } else if (sampler.ArraySize != 1) @@ -241,7 +243,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv if (image.ArraySize == 0) { - var imageArrayType = context.TypeRuntimeArray(imageType); + var imageArrayType = context.HostCapabilities.HasUnsizedDescriptorArrayBug + ? context.TypeArray(imageType, context.Constant(context.TypeU32(), 1)) + : context.TypeRuntimeArray(imageType); imageArrayPointerType = context.TypePointer(StorageClass.UniformConstant, imageArrayType); } else if (image.ArraySize != 1) diff --git a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs index 2df2b1442..22572ad51 100644 --- a/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs +++ b/src/Ryujinx.Graphics.Shader/IGpuAccessor.cs @@ -195,6 +195,15 @@ namespace Ryujinx.Graphics.Shader return false; } + /// + /// Queries host about the presence of the unsized descriptor array bug. + /// + /// True if the bug is present on the host device used, false otherwise + bool QueryHostHasUnsizedDescriptorArrayBug() + { + return false; + } + /// /// Queries host about the presence of the vector indexing bug. /// @@ -231,6 +240,15 @@ namespace Ryujinx.Graphics.Shader return true; } + /// + /// Queries host support for accessing bindless textures. + /// + /// True if bindless textures are supported, false otherwise + bool QueryHostSupportsBindlessTextures() + { + return true; + } + /// /// Queries host support for fragment shader ordering critical sections on the shader code. /// diff --git a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs index 2523272b0..ccdec2210 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/HostCapabilities.cs @@ -3,6 +3,7 @@ namespace Ryujinx.Graphics.Shader.Translation class HostCapabilities { public readonly bool ReducedPrecision; + public readonly bool HasUnsizedDescriptorArrayBug; public readonly bool SupportsFragmentShaderInterlock; public readonly bool SupportsFragmentShaderOrderingIntel; public readonly bool SupportsGeometryShaderPassthrough; @@ -13,6 +14,7 @@ namespace Ryujinx.Graphics.Shader.Translation public HostCapabilities( bool reducedPrecision, + bool hasUnsizedDescriptorArrayBug, bool supportsFragmentShaderInterlock, bool supportsFragmentShaderOrderingIntel, bool supportsGeometryShaderPassthrough, @@ -22,6 +24,7 @@ namespace Ryujinx.Graphics.Shader.Translation bool supportsViewportMask) { ReducedPrecision = reducedPrecision; + HasUnsizedDescriptorArrayBug = hasUnsizedDescriptorArrayBug; SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel; SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough; diff --git a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs index 6abe1532a..0c2677b89 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/ResourceManager.cs @@ -195,12 +195,14 @@ namespace Ryujinx.Graphics.Shader.Translation private void AddBindlessDefinition(int set, int binding, string name, SamplerType samplerType) { TextureDefinition definition = new(set, binding, name, samplerType, TextureFormat.Unknown, TextureUsageFlags.None, 0); + Properties.AddOrUpdateTexture(definition, samplerType & ~(SamplerType.Separate | SamplerType.Shadow)); } private void AddBindlessSeparateDefinition(int set, int binding, string name, SamplerType samplerType) { samplerType = (samplerType & ~SamplerType.Shadow) | SamplerType.Separate; + AddBindlessDefinition(set, binding, name, samplerType); } @@ -212,6 +214,7 @@ namespace Ryujinx.Graphics.Shader.Translation binding = _gpuAccessor.QueryBindingConstantBuffer(slot); _cbSlotToBindingMap[slot] = binding; string slotNumber = slot.ToString(CultureInfo.InvariantCulture); + AddNewConstantBuffer(binding, $"{_stagePrefix}_c{slotNumber}"); } @@ -234,6 +237,7 @@ namespace Ryujinx.Graphics.Shader.Translation binding = _gpuAccessor.QueryBindingStorageBuffer(slot); _sbSlotToBindingMap[slot] = binding; string slotNumber = slot.ToString(CultureInfo.InvariantCulture); + AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}"); } diff --git a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs index 706c3347b..495b331d7 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/Transforms/TexturePass.cs @@ -24,7 +24,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms ref context.BindlessTextureFlags, ref context.BindlessIndexedBuffersMask, context.BindlessTexturesAllowed, - context.GpuAccessor.QueryTextureBufferIndex()); + context.GpuAccessor.QueryTextureBufferIndex(), + context.GpuAccessor.QueryHostSupportsBindlessTextures()); if (prevNode != node) { @@ -787,7 +788,8 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms ref BindlessTextureFlags bindlessTextureFlags, ref uint bindlessIndexedBuffersMask, bool bindlessTexturesAllowed, - int textureBufferIndex) + int textureBufferIndex, + bool supportsBindlessTextures) { if (node.Value is not TextureOperation texOp) { @@ -797,35 +799,36 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms // If it's already bindless, then we have nothing to do. if (texOp.Flags.HasFlag(TextureFlags.Bindless)) { - resourceManager.EnsureBindlessBinding(targetApi, texOp.Type, texOp.Inst.IsImage()); - - if (IsIndexedAccess(resourceManager, texOp, ref bindlessTextureFlags, ref bindlessIndexedBuffersMask, textureBufferIndex)) + if (SupportsBindlessAccess(texOp.Type, supportsBindlessTextures)) { - return node; - } + resourceManager.EnsureBindlessBinding(targetApi, texOp.Type, texOp.Inst.IsImage()); - if (bindlessTexturesAllowed) - { - bindlessTextureFlags |= BindlessTextureFlags.BindlessFull; - return node; - } - else - { - // Set any destination operand to zero and remove the texture access. - // This is a case where bindless elimination failed, and we assume - // it's too risky to try using full bindless emulation. - - for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++) + if (IsIndexedAccess(resourceManager, texOp, ref bindlessTextureFlags, ref bindlessIndexedBuffersMask, textureBufferIndex)) { - Operand dest = texOp.GetDest(destIndex); - node.List.AddBefore(node, new Operation(Instruction.Copy, dest, Const(0))); + return node; } - LinkedListNode prevNode = node.Previous; - node.List.Remove(node); - - return prevNode; + if (bindlessTexturesAllowed) + { + bindlessTextureFlags |= BindlessTextureFlags.BindlessFull; + return node; + } } + + // Set any destination operand to zero and remove the texture access. + // This is a case where bindless elimination failed, and we assume + // it's too risky or not possible to try using full bindless emulation. + + for (int destIndex = 0; destIndex < texOp.DestsCount; destIndex++) + { + Operand dest = texOp.GetDest(destIndex); + node.List.AddBefore(node, new Operation(Instruction.Copy, dest, Const(0))); + } + + LinkedListNode prevNode = node.Previous; + node.List.Remove(node); + + return prevNode; } // If the index is within the host API limits, then we don't need to make it bindless. @@ -866,6 +869,17 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms return node; } + private static bool SupportsBindlessAccess(SamplerType type, bool supportsBindlessTextures) + { + // TODO: Support bindless buffer texture access. + if ((type & SamplerType.Mask) == SamplerType.TextureBuffer) + { + return false; + } + + return supportsBindlessTextures; + } + private static bool IsIndexedAccess( ResourceManager resourceManager, TextureOperation texOp, diff --git a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs index d198f8f9c..2fb16141a 100644 --- a/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs +++ b/src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs @@ -371,6 +371,7 @@ namespace Ryujinx.Graphics.Shader.Translation var hostCapabilities = new HostCapabilities( GpuAccessor.QueryHostReducedPrecision(), + GpuAccessor.QueryHostHasUnsizedDescriptorArrayBug(), GpuAccessor.QueryHostSupportsFragmentShaderInterlock(), GpuAccessor.QueryHostSupportsFragmentShaderOrderingIntel(), GpuAccessor.QueryHostSupportsGeometryShaderPassthrough(), diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 84cb38b31..b8aa589fd 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -572,10 +572,13 @@ namespace Ryujinx.Graphics.Vulkan var limits = _physicalDevice.PhysicalDeviceProperties.Limits; + bool supportsDescriptorIndexing = _physicalDevice.IsDeviceExtensionPresent("VK_EXT_descriptor_indexing"); + return new Capabilities( api: TargetApi.Vulkan, GpuVendor, hasFrontFacingBug: IsIntelWindows, + hasUnsizedDescriptorArrayBug: IsMoltenVk, hasVectorIndexingBug: Vendor == Vendor.Qualcomm, needsFragmentOutputSpecialization: IsMoltenVk, reduceShaderPrecision: IsMoltenVk, @@ -590,6 +593,7 @@ namespace Ryujinx.Graphics.Vulkan supportsR4G4B4A4Format: supportsR4G4B4A4Format, supportsSnormBufferTextureFormat: true, supports5BitComponentFormat: supports5BitComponentFormat, + supportsBindlessTextures: supportsDescriptorIndexing, supportsBlendEquationAdvanced: Capabilities.SupportsBlendEquationAdvanced, supportsFragmentShaderInterlock: Capabilities.SupportsFragmentShaderInterlock, supportsFragmentShaderOrderingIntel: false,