diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs index 7b4b1e8a4..0386df953 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Compute.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs @@ -4,7 +4,6 @@ using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Gpu.State; using Ryujinx.Graphics.Shader; using System; -using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Engine { @@ -91,9 +90,7 @@ namespace Ryujinx.Graphics.Gpu.Engine cbDescAddress += (ulong)cbDescOffset; - ReadOnlySpan cbDescriptorData = _context.PhysicalMemory.GetSpan(cbDescAddress, 0x10); - - SbDescriptor cbDescriptor = MemoryMarshal.Cast(cbDescriptorData)[0]; + SbDescriptor cbDescriptor = _context.PhysicalMemory.Read(cbDescAddress); BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size); } @@ -110,9 +107,7 @@ namespace Ryujinx.Graphics.Gpu.Engine sbDescAddress += (ulong)sbDescOffset; - ReadOnlySpan sbDescriptorData = _context.PhysicalMemory.GetSpan(sbDescAddress, 0x10); - - SbDescriptor sbDescriptor = MemoryMarshal.Cast(sbDescriptorData)[0]; + SbDescriptor sbDescriptor = _context.PhysicalMemory.Read(sbDescAddress); BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size); } diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs index acb5ad5c4..733f15c12 100644 --- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs +++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs @@ -292,9 +292,7 @@ namespace Ryujinx.Graphics.Gpu.Engine sbDescAddress += (ulong)sbDescOffset; - ReadOnlySpan sbDescriptorData = _context.PhysicalMemory.GetSpan(sbDescAddress, 0x10); - - SbDescriptor sbDescriptor = MemoryMarshal.Cast(sbDescriptorData)[0]; + SbDescriptor sbDescriptor = _context.PhysicalMemory.Read(sbDescAddress); BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size); } diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs index 2abf96de9..ca13a7d6f 100644 --- a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs +++ b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs @@ -1,6 +1,3 @@ -using System; -using System.Runtime.InteropServices; - namespace Ryujinx.Graphics.Gpu.Image { /// @@ -41,11 +38,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (sampler == null) { - ulong address = Address + (ulong)(uint)id * DescriptorSize; - - ReadOnlySpan data = Context.PhysicalMemory.GetSpan(address, DescriptorSize); - - SamplerDescriptor descriptor = MemoryMarshal.Cast(data)[0]; + SamplerDescriptor descriptor = Context.PhysicalMemory.Read(Address + (ulong)id * DescriptorSize); sampler = new Sampler(Context, descriptor); diff --git a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs index 612ec5ca8..672544409 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs @@ -1,8 +1,6 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.State; using Ryujinx.Graphics.Shader; -using System; -using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Image { @@ -11,6 +9,9 @@ namespace Ryujinx.Graphics.Gpu.Image /// class TextureBindingsManager { + private const int HandleHigh = 16; + private const int HandleMask = (1 << HandleHigh) - 1; + private GpuContext _context; private bool _isCompute; @@ -114,7 +115,6 @@ namespace Ryujinx.Graphics.Gpu.Image } _samplerPool = new SamplerPool(_context, address, maximumId); - _samplerIndex = samplerIndex; } @@ -195,7 +195,7 @@ namespace Ryujinx.Graphics.Gpu.Image address = bufferManager.GetGraphicsUniformBufferAddress(stageIndex, binding.CbufSlot); } - packedId = MemoryMarshal.Cast(_context.PhysicalMemory.GetSpan(address + (ulong)binding.CbufOffset * 4, 4))[0]; + packedId = _context.PhysicalMemory.Read(address + (ulong)binding.CbufOffset * 4); } else { @@ -324,9 +324,20 @@ namespace Ryujinx.Graphics.Gpu.Image address = bufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex); } - address += (uint)wordOffset * 4; + int handle = _context.PhysicalMemory.Read(address + (ulong)(wordOffset & HandleMask) * 4); - return BitConverter.ToInt32(_context.PhysicalMemory.GetSpan(address, 4)); + // The "wordOffset" (which is really the immediate value used on texture instructions on the shader) + // is a 13-bit value. However, in order to also support separate samplers and textures (which uses + // bindless textures on the shader), we extend it with another value on the higher 16 bits with + // another offset for the sampler. + // The shader translator has code to detect separate texture and sampler uses with a bindless texture, + // turn that into a regular texture access and produce those special handles with values on the higher 16 bits. + if (wordOffset >> HandleHigh != 0) + { + handle |= _context.PhysicalMemory.Read(address + (ulong)(wordOffset >> HandleHigh) * 4); + } + + return handle; } /// diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs index 600e2f5bc..c0eeb0680 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs @@ -773,6 +773,22 @@ namespace Ryujinx.Graphics.Gpu.Image } } + if (info.Target == Target.TextureBuffer) + { + // We assume that the host does not support signed normalized format + // (as is the case with OpenGL), so we just use a unsigned format. + // The shader will need the appropriate conversion code to compensate. + switch (formatInfo.Format) + { + case Format.R8Snorm: formatInfo = new FormatInfo(Format.R8Sint, 1, 1, 1); break; + case Format.R16Snorm: formatInfo = new FormatInfo(Format.R16Sint, 1, 1, 2); break; + case Format.R8G8Snorm: formatInfo = new FormatInfo(Format.R8G8Sint, 1, 1, 2); break; + case Format.R16G16Snorm: formatInfo = new FormatInfo(Format.R16G16Sint, 1, 1, 4); break; + case Format.R8G8B8A8Snorm: formatInfo = new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4); break; + case Format.R16G16B16A16Snorm: formatInfo = new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8); break; + } + } + int width = info.Width / info.SamplesInX; int height = info.Height / info.SamplesInY; diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index f81c67ef7..1f0ae75a5 100644 --- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -1,9 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Memory; -using System; using System.Collections.Generic; -using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Image { @@ -83,11 +81,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// The texture descriptor public TextureDescriptor GetDescriptor(int id) { - ulong address = Address + (ulong)(uint)id * DescriptorSize; - - ReadOnlySpan data = Context.PhysicalMemory.GetSpan(address, DescriptorSize); - - return MemoryMarshal.Cast(data)[0]; + return Context.PhysicalMemory.Read(Address + (ulong)id * DescriptorSize); } /// @@ -107,9 +101,7 @@ namespace Ryujinx.Graphics.Gpu.Image if (texture != null) { - ReadOnlySpan data = Context.PhysicalMemory.GetSpan(address, DescriptorSize); - - TextureDescriptor descriptor = MemoryMarshal.Cast(data)[0]; + TextureDescriptor descriptor = Context.PhysicalMemory.Read(address); // If the descriptors are the same, the texture is the same, // we don't need to remove as it was not modified. Just continue. diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs index cfc6f7f50..38f448d90 100644 --- a/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Memory/MemoryAccessor.cs @@ -46,11 +46,11 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// - /// Reads a structure from GPU mapped memory. + /// Reads data from GPU mapped memory. /// - /// Type of the structure - /// GPU virtual address where the structure is located - /// The structure at the specified memory location + /// Type of the data + /// GPU virtual address where the data is located + /// The data at the specified memory location public T Read(ulong gpuVa) where T : unmanaged { ulong processVa = _context.MemoryManager.Translate(gpuVa); @@ -67,7 +67,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { ulong processVa = _context.MemoryManager.Translate(gpuVa); - return BitConverter.ToInt32(_context.PhysicalMemory.GetSpan(processVa, 4)); + return _context.PhysicalMemory.Read(processVa); } /// @@ -79,7 +79,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { ulong processVa = _context.MemoryManager.Translate(gpuVa); - return BitConverter.ToUInt64(_context.PhysicalMemory.GetSpan(processVa, 8)); + return _context.PhysicalMemory.Read(processVa); } /// diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs index 5d9b55618..4a80aa1a5 100644 --- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs +++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Memory { @@ -33,6 +34,17 @@ namespace Ryujinx.Graphics.Gpu.Memory return _cpuMemory.GetSpan(address, size); } + /// + /// Reads data from the application process. + /// + /// Type of the structure + /// Address to read from + /// The data at the specified memory location + public T Read(ulong address) where T : unmanaged + { + return MemoryMarshal.Cast(GetSpan(address, Unsafe.SizeOf()))[0]; + } + /// /// Writes data to the application process. /// diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs index a45c10c3e..bd947ab71 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs @@ -324,6 +324,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl if (!images.TryAdd(imageName, texOp)) { + // Ensure that all texture operations share the same format. + // This avoid errors like mismatched formats. + texOp.Format = images[imageName].Format; + continue; } diff --git a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs index e9a37d87e..4c3f802a8 100644 --- a/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs +++ b/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs @@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl } else { - suffix = texOp.Handle.ToString(); + suffix = texOp.Handle.ToString("X"); if ((texOp.Type & SamplerType.Indexed) != 0) { @@ -242,7 +242,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl public static string GetImageName(ShaderStage stage, AstTextureOperation texOp, string indexExpr) { - string suffix = texOp.Handle.ToString(); + string suffix = texOp.Handle.ToString("X"); if ((texOp.Type & SamplerType.Indexed) != 0) { diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs index 7bed3f30c..43e5822e7 100644 --- a/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs +++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs @@ -879,6 +879,8 @@ namespace Ryujinx.Graphics.Shader.Instructions if (op.IsBindless) { sourcesList.Add(Ra()); + + flags |= TextureFlags.Bindless; } SamplerType type = ConvertSamplerType(op.Dimensions); @@ -1081,6 +1083,24 @@ namespace Ryujinx.Graphics.Shader.Instructions SamplerType type = ConvertSamplerType(op.Dimensions); + bool hasLod = op.LodMode > TextureLodMode.LodZero; + + if (type == SamplerType.Texture1D && (flags & ~TextureFlags.Bindless) == TextureFlags.IntCoords && !(hasLod || + op.HasDepthCompare || + op.HasOffset || + op.IsArray || + op.IsMultisample)) + { + // For bindless, we don't have any way to know the texture type, + // so we assume it's texture buffer when the sampler type is 1D, since that's more common. + bool isTypeBuffer = isBindless || context.Config.GpuAccessor.QueryIsTextureBuffer(op.Immediate); + + if (isTypeBuffer) + { + type = SamplerType.TextureBuffer; + } + } + int coordsCount = type.GetDimensions(); for (int index = 0; index < coordsCount; index++) @@ -1095,8 +1115,6 @@ namespace Ryujinx.Graphics.Shader.Instructions type |= SamplerType.Array; } - bool hasLod = op.LodMode > TextureLodMode.LodZero; - Operand lodValue = hasLod ? Rb() : ConstF(0); Operand packedOffs = op.HasOffset ? Rb() : null; @@ -1110,7 +1128,7 @@ namespace Ryujinx.Graphics.Shader.Instructions if ((op.LodMode == TextureLodMode.LodZero || op.LodMode == TextureLodMode.LodLevel || - op.LodMode == TextureLodMode.LodLevelA) && !op.IsMultisample) + op.LodMode == TextureLodMode.LodLevelA) && !op.IsMultisample && type != SamplerType.TextureBuffer) { sourcesList.Add(lodValue); @@ -1142,16 +1160,6 @@ namespace Ryujinx.Graphics.Shader.Instructions type |= SamplerType.Multisample; } - if (type == SamplerType.Texture1D && flags == TextureFlags.IntCoords && !isBindless) - { - bool isTypeBuffer = context.Config.GpuAccessor.QueryIsTextureBuffer(op.Immediate); - - if (isTypeBuffer) - { - type = SamplerType.TextureBuffer; - } - } - Operand[] sources = sourcesList.ToArray(); int rdIndex = op.Rd.Index; diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs index 6b7fb82fe..2c4a88cdb 100644 --- a/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs @@ -1,3 +1,5 @@ +using System; + namespace Ryujinx.Graphics.Shader.IntermediateRepresentation { class Operation : INode @@ -78,6 +80,18 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation _sources[index] = source; } + protected void RemoveSource(int index) + { + SetSource(index, null); + + Operand[] newSources = new Operand[_sources.Length - 1]; + + Array.Copy(_sources, 0, newSources, 0, index); + Array.Copy(_sources, index + 1, newSources, index, _sources.Length - (index + 1)); + + _sources = newSources; + } + public void TurnIntoCopy(Operand source) { TurnInto(Instruction.Copy, source); diff --git a/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs index 06541f909..9c5cd25c1 100644 --- a/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs +++ b/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs @@ -26,8 +26,18 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation public void TurnIntoIndexed(int handle) { Type |= SamplerType.Indexed; - Flags &= ~TextureFlags.Bindless; + Handle = handle; + } + + public void SetHandle(int handle) + { + if ((Flags & TextureFlags.Bindless) != 0) + { + Flags &= ~TextureFlags.Bindless; + + RemoveSource(0); + } Handle = handle; } diff --git a/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs index a3fa3e3a6..bb935ce77 100644 --- a/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs +++ b/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs @@ -5,7 +5,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr class AstTextureOperation : AstOperation { public SamplerType Type { get; } - public TextureFormat Format { get; } + public TextureFormat Format { get; set; } public TextureFlags Flags { get; } public int Handle { get; } diff --git a/Ryujinx.Graphics.Shader/Translation/Lowering.cs b/Ryujinx.Graphics.Shader/Translation/Lowering.cs index 6f52ce965..6da25983a 100644 --- a/Ryujinx.Graphics.Shader/Translation/Lowering.cs +++ b/Ryujinx.Graphics.Shader/Translation/Lowering.cs @@ -1,6 +1,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; using static Ryujinx.Graphics.Shader.Translation.GlobalMemory; @@ -27,9 +28,17 @@ namespace Ryujinx.Graphics.Shader.Translation node = RewriteGlobalAccess(node, config); } - if (operation.Inst == Instruction.TextureSample) + if (operation is TextureOperation texOp) { - node = RewriteTextureSample(node, config); + if (texOp.Inst == Instruction.TextureSample) + { + node = RewriteTextureSample(node, config); + } + + if (texOp.Type == SamplerType.TextureBuffer) + { + node = InsertSnormNormalization(node, config); + } } } } @@ -419,5 +428,57 @@ namespace Ryujinx.Graphics.Shader.Translation return node; } + + private static LinkedListNode InsertSnormNormalization(LinkedListNode node, ShaderConfig config) + { + TextureOperation texOp = (TextureOperation)node.Value; + + TextureFormat format = config.GpuAccessor.QueryTextureFormat(texOp.Handle); + + int maxPositive = format switch + { + TextureFormat.R8Snorm => sbyte.MaxValue, + TextureFormat.R8G8Snorm => sbyte.MaxValue, + TextureFormat.R8G8B8A8Snorm => sbyte.MaxValue, + TextureFormat.R16Snorm => short.MaxValue, + TextureFormat.R16G16Snorm => short.MaxValue, + TextureFormat.R16G16B16A16Snorm => short.MaxValue, + _ => 0 + }; + + // The value being 0 means that the format is not a SNORM format, so there's nothing to do here. + if (maxPositive == 0) + { + return node; + } + + // Do normalization. We assume SINT formats are being used as replacement for SNORM (that is not supported). + INode[] uses = texOp.Dest.UseOps.ToArray(); + + Operation convOp = new Operation(Instruction.ConvertS32ToFP, Local(), texOp.Dest); + Operation normOp = new Operation(Instruction.FP32 | Instruction.Multiply, Local(), convOp.Dest, ConstF(1f / maxPositive)); + + node = node.List.AddAfter(node, convOp); + node = node.List.AddAfter(node, normOp); + + foreach (INode useOp in uses) + { + if (!(useOp is Operation op)) + { + continue; + } + + // Replace all uses of the texture pixel value with the normalized value. + for (int index = 0; index < op.SourcesCount; index++) + { + if (op.GetSource(index) == texOp.Dest) + { + op.SetSource(index, normOp.Dest); + } + } + } + + return node; + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs new file mode 100644 index 000000000..9515c349c --- /dev/null +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs @@ -0,0 +1,50 @@ +using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Shader.Translation.Optimizations +{ + class BindlessElimination + { + public static void RunPass(BasicBlock block) + { + // We can turn a bindless into regular access by recognizing the pattern + // produced by the compiler for separate texture and sampler. + // We check for the following conditions: + // - The handle is the result of a bitwise OR logical operation. + // - Both sources of the OR operation comes from CB2 (used by NVN to hold texture handles). + for (LinkedListNode node = block.Operations.First; node != null; node = node.Next) + { + if (!(node.Value is TextureOperation texOp)) + { + continue; + } + + if ((texOp.Flags & TextureFlags.Bindless) == 0) + { + continue; + } + + if (!(texOp.GetSource(0).AsgOp is Operation handleCombineOp)) + { + continue; + } + + if (handleCombineOp.Inst != Instruction.BitwiseOr) + { + continue; + } + + Operand src0 = handleCombineOp.GetSource(0); + Operand src1 = handleCombineOp.GetSource(1); + + if (src0.Type != OperandType.ConstantBuffer || src0.GetCbufSlot() != 2 || + src1.Type != OperandType.ConstantBuffer || src1.GetCbufSlot() != 2) + { + continue; + } + + texOp.SetHandle(src0.GetCbufOffset() | (src1.GetCbufOffset() << 16)); + } + } + } +} diff --git a/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs b/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs index c5db4678b..10a0e7801 100644 --- a/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs +++ b/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs @@ -1,4 +1,5 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -88,6 +89,22 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) { BindlessToIndexed.RunPass(blocks[blkIndex]); + BindlessElimination.RunPass(blocks[blkIndex]); + + // Try to eliminate any operations that are now unused. + LinkedListNode node = blocks[blkIndex].Operations.First; + + while (node != null) + { + LinkedListNode nextNode = node.Next; + + if (IsUnused(node.Value)) + { + RemoveNode(blocks[blkIndex], node); + } + + node = nextNode; + } } }