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  }