BindlessToArray.cs
1 using Ryujinx.Graphics.Shader.IntermediateRepresentation; 2 using System; 3 using System.Collections.Generic; 4 using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper; 5 6 namespace Ryujinx.Graphics.Shader.Translation.Optimizations 7 { 8 static class BindlessToArray 9 { 10 private const int NvnTextureBufferIndex = 2; 11 private const int HardcodedArrayLengthOgl = 4; 12 13 // 1 and 0 elements are not considered arrays anymore. 14 public const int MinimumArrayLength = 2; 15 16 public static void RunPassOgl(BasicBlock block, ResourceManager resourceManager) 17 { 18 // We can turn a bindless texture access into a indexed access, 19 // as long the following conditions are true: 20 // - The handle is loaded using a LDC instruction. 21 // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). 22 // - The load has a constant offset. 23 // The base offset of the array of handles on the constant buffer is the constant offset. 24 for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next) 25 { 26 if (node.Value is not TextureOperation texOp) 27 { 28 continue; 29 } 30 31 if ((texOp.Flags & TextureFlags.Bindless) == 0) 32 { 33 continue; 34 } 35 36 if (texOp.GetSource(0).AsgOp is not Operation handleAsgOp) 37 { 38 continue; 39 } 40 41 if (handleAsgOp.Inst != Instruction.Load || 42 handleAsgOp.StorageKind != StorageKind.ConstantBuffer || 43 handleAsgOp.SourcesCount != 4) 44 { 45 continue; 46 } 47 48 Operand ldcSrc0 = handleAsgOp.GetSource(0); 49 50 if (ldcSrc0.Type != OperandType.Constant || 51 !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot) || 52 src0CbufSlot != NvnTextureBufferIndex) 53 { 54 continue; 55 } 56 57 Operand ldcSrc1 = handleAsgOp.GetSource(1); 58 59 // We expect field index 0 to be accessed. 60 if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0) 61 { 62 continue; 63 } 64 65 Operand ldcSrc2 = handleAsgOp.GetSource(2); 66 67 // FIXME: This is missing some checks, for example, a check to ensure that the shift value is 2. 68 // Might be not worth fixing since if that doesn't kick in, the result will be no texture 69 // to access anyway which is also wrong. 70 // Plus this whole transform is fundamentally flawed as-is since we have no way to know the array size. 71 // Eventually, this should be entirely removed in favor of a implementation that supports true bindless 72 // texture access. 73 if (ldcSrc2.AsgOp is not Operation shrOp || shrOp.Inst != Instruction.ShiftRightU32) 74 { 75 continue; 76 } 77 78 if (shrOp.GetSource(0).AsgOp is not Operation shrOp2 || shrOp2.Inst != Instruction.ShiftRightU32) 79 { 80 continue; 81 } 82 83 if (shrOp2.GetSource(0).AsgOp is not Operation addOp || addOp.Inst != Instruction.Add) 84 { 85 continue; 86 } 87 88 Operand addSrc1 = addOp.GetSource(1); 89 90 if (addSrc1.Type != OperandType.Constant) 91 { 92 continue; 93 } 94 95 TurnIntoArray(resourceManager, texOp, NvnTextureBufferIndex, addSrc1.Value / 4, HardcodedArrayLengthOgl); 96 97 Operand index = Local(); 98 99 Operand source = addOp.GetSource(0); 100 101 Operation shrBy3 = new(Instruction.ShiftRightU32, index, source, Const(3)); 102 103 block.Operations.AddBefore(node, shrBy3); 104 105 texOp.SetSource(0, index); 106 } 107 } 108 109 public static void RunPass(BasicBlock block, ResourceManager resourceManager, IGpuAccessor gpuAccessor) 110 { 111 // We can turn a bindless texture access into a indexed access, 112 // as long the following conditions are true: 113 // - The handle is loaded using a LDC instruction. 114 // - The handle is loaded from the constant buffer with the handles (CB2 for NVN). 115 // - The load has a constant offset. 116 // The base offset of the array of handles on the constant buffer is the constant offset. 117 for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next) 118 { 119 if (node.Value is not TextureOperation texOp) 120 { 121 continue; 122 } 123 124 if ((texOp.Flags & TextureFlags.Bindless) == 0) 125 { 126 continue; 127 } 128 129 Operand bindlessHandle = Utils.FindLastOperation(texOp.GetSource(0), block); 130 131 if (bindlessHandle.AsgOp is not Operation handleAsgOp) 132 { 133 continue; 134 } 135 136 int secondaryCbufSlot = 0; 137 int secondaryCbufOffset = 0; 138 bool hasSecondaryHandle = false; 139 140 if (handleAsgOp.Inst == Instruction.BitwiseOr) 141 { 142 Operand src0 = Utils.FindLastOperation(handleAsgOp.GetSource(0), block); 143 Operand src1 = Utils.FindLastOperation(handleAsgOp.GetSource(1), block); 144 145 if (src0.Type == OperandType.ConstantBuffer && src1.AsgOp is Operation) 146 { 147 handleAsgOp = src1.AsgOp as Operation; 148 secondaryCbufSlot = src0.GetCbufSlot(); 149 secondaryCbufOffset = src0.GetCbufOffset(); 150 hasSecondaryHandle = true; 151 } 152 else if (src0.AsgOp is Operation && src1.Type == OperandType.ConstantBuffer) 153 { 154 handleAsgOp = src0.AsgOp as Operation; 155 secondaryCbufSlot = src1.GetCbufSlot(); 156 secondaryCbufOffset = src1.GetCbufOffset(); 157 hasSecondaryHandle = true; 158 } 159 } 160 161 if (handleAsgOp.Inst != Instruction.Load || 162 handleAsgOp.StorageKind != StorageKind.ConstantBuffer || 163 handleAsgOp.SourcesCount != 4) 164 { 165 continue; 166 } 167 168 Operand ldcSrc0 = handleAsgOp.GetSource(0); 169 170 if (ldcSrc0.Type != OperandType.Constant || 171 !resourceManager.TryGetConstantBufferSlot(ldcSrc0.Value, out int src0CbufSlot)) 172 { 173 continue; 174 } 175 176 Operand ldcSrc1 = handleAsgOp.GetSource(1); 177 178 // We expect field index 0 to be accessed. 179 if (ldcSrc1.Type != OperandType.Constant || ldcSrc1.Value != 0) 180 { 181 continue; 182 } 183 184 Operand ldcVecIndex = handleAsgOp.GetSource(2); 185 Operand ldcElemIndex = handleAsgOp.GetSource(3); 186 187 if (ldcVecIndex.Type != OperandType.LocalVariable || ldcElemIndex.Type != OperandType.LocalVariable) 188 { 189 continue; 190 } 191 192 int cbufSlot; 193 int handleIndex; 194 195 if (hasSecondaryHandle) 196 { 197 cbufSlot = TextureHandle.PackSlots(src0CbufSlot, secondaryCbufSlot); 198 handleIndex = TextureHandle.PackOffsets(0, secondaryCbufOffset, TextureHandleType.SeparateSamplerHandle); 199 } 200 else 201 { 202 cbufSlot = src0CbufSlot; 203 handleIndex = 0; 204 } 205 206 int length = Math.Max(MinimumArrayLength, gpuAccessor.QueryTextureArrayLengthFromBuffer(src0CbufSlot)); 207 208 TurnIntoArray(resourceManager, texOp, cbufSlot, handleIndex, length); 209 210 Operand vecIndex = Local(); 211 Operand elemIndex = Local(); 212 Operand index = Local(); 213 Operand indexMin = Local(); 214 215 block.Operations.AddBefore(node, new Operation(Instruction.ShiftLeft, vecIndex, ldcVecIndex, Const(1))); 216 block.Operations.AddBefore(node, new Operation(Instruction.ShiftRightU32, elemIndex, ldcElemIndex, Const(1))); 217 block.Operations.AddBefore(node, new Operation(Instruction.Add, index, vecIndex, elemIndex)); 218 block.Operations.AddBefore(node, new Operation(Instruction.MinimumU32, indexMin, index, Const(length - 1))); 219 220 texOp.SetSource(0, indexMin); 221 } 222 } 223 224 private static void TurnIntoArray(ResourceManager resourceManager, TextureOperation texOp, int cbufSlot, int handleIndex, int length) 225 { 226 SetBindingPair setAndBinding = resourceManager.GetTextureOrImageBinding( 227 texOp.Inst, 228 texOp.Type, 229 texOp.Format, 230 texOp.Flags & ~TextureFlags.Bindless, 231 cbufSlot, 232 handleIndex, 233 length); 234 235 texOp.TurnIntoArray(setAndBinding); 236 } 237 } 238 }