TexturePass.cs
  1  using Ryujinx.Graphics.Shader.IntermediateRepresentation;
  2  using System.Collections.Generic;
  3  using System.Linq;
  4  using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
  5  
  6  namespace Ryujinx.Graphics.Shader.Translation.Transforms
  7  {
  8      class TexturePass : ITransformPass
  9      {
 10          public static bool IsEnabled(IGpuAccessor gpuAccessor, ShaderStage stage, TargetLanguage targetLanguage, FeatureFlags usedFeatures)
 11          {
 12              return true;
 13          }
 14  
 15          public static LinkedListNode<INode> RunPass(TransformContext context, LinkedListNode<INode> node)
 16          {
 17              if (node.Value is TextureOperation texOp)
 18              {
 19                  node = InsertTexelFetchScale(context.Hfm, node, context.ResourceManager, context.Stage);
 20                  node = InsertTextureSizeUnscale(context.Hfm, node, context.ResourceManager, context.Stage);
 21  
 22                  if (texOp.Inst == Instruction.TextureSample)
 23                  {
 24                      node = InsertCoordNormalization(context.Hfm, node, context.ResourceManager, context.GpuAccessor, context.Stage);
 25                      node = InsertCoordGatherBias(node, context.ResourceManager, context.GpuAccessor);
 26                      node = InsertConstOffsets(node, context.ResourceManager, context.GpuAccessor, context.Stage);
 27  
 28                      if (texOp.Type == SamplerType.TextureBuffer && !context.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat())
 29                      {
 30                          node = InsertSnormNormalization(node, context.ResourceManager, context.GpuAccessor);
 31                      }
 32                  }
 33              }
 34  
 35              return node;
 36          }
 37  
 38          private static LinkedListNode<INode> InsertTexelFetchScale(
 39              HelperFunctionManager hfm,
 40              LinkedListNode<INode> node,
 41              ResourceManager resourceManager,
 42              ShaderStage stage)
 43          {
 44              TextureOperation texOp = (TextureOperation)node.Value;
 45  
 46              bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
 47              bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
 48  
 49              bool isImage = IsImageInstructionWithScale(texOp.Inst);
 50              bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage);
 51  
 52              if ((texOp.Inst == Instruction.TextureSample || isImage) &&
 53                  (intCoords || isImage) &&
 54                  !isBindless &&
 55                  !isIndexed &&
 56                  stage.SupportsRenderScale() &&
 57                  TypeSupportsScale(texOp.Type))
 58              {
 59                  int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
 60                  int samplerIndex = isImage
 61                      ? resourceManager.GetTextureDescriptors(includeArrays: false).Length + resourceManager.FindImageDescriptorIndex(texOp.Binding)
 62                      : resourceManager.FindTextureDescriptorIndex(texOp.Binding);
 63  
 64                  int coordsCount = texOp.Type.GetDimensions();
 65                  int coordsIndex = isBindless ? 1 : 0;
 66  
 67                  for (int index = 0; index < coordsCount; index++)
 68                  {
 69                      Operand scaledCoord = Local();
 70                      Operand[] callArgs;
 71  
 72                      if (stage == ShaderStage.Fragment)
 73                      {
 74                          callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex), Const(index) };
 75                      }
 76                      else
 77                      {
 78                          callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex) };
 79                      }
 80  
 81                      node.List.AddBefore(node, new Operation(Instruction.Call, 0, scaledCoord, callArgs));
 82  
 83                      texOp.SetSource(coordsIndex + index, scaledCoord);
 84                  }
 85              }
 86  
 87              return node;
 88          }
 89  
 90          private static LinkedListNode<INode> InsertTextureSizeUnscale(
 91              HelperFunctionManager hfm,
 92              LinkedListNode<INode> node,
 93              ResourceManager resourceManager,
 94              ShaderStage stage)
 95          {
 96              TextureOperation texOp = (TextureOperation)node.Value;
 97  
 98              bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
 99              bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
100  
101              if (texOp.Inst == Instruction.TextureQuerySize &&
102                  texOp.Index < 2 &&
103                  !isBindless &&
104                  !isIndexed &&
105                  stage.SupportsRenderScale() &&
106                  TypeSupportsScale(texOp.Type))
107              {
108                  int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale);
109                  int samplerIndex = resourceManager.FindTextureDescriptorIndex(texOp.Binding);
110  
111                  for (int index = texOp.DestsCount - 1; index >= 0; index--)
112                  {
113                      Operand dest = texOp.GetDest(index);
114  
115                      Operand unscaledSize = Local();
116  
117                      // Replace all uses with the unscaled size value.
118                      // This must be done before the call is added, since it also is a use of the original size.
119                      foreach (INode useOp in dest.UseOps)
120                      {
121                          for (int srcIndex = 0; srcIndex < useOp.SourcesCount; srcIndex++)
122                          {
123                              if (useOp.GetSource(srcIndex) == dest)
124                              {
125                                  useOp.SetSource(srcIndex, unscaledSize);
126                              }
127                          }
128                      }
129  
130                      Operand[] callArgs = new Operand[] { Const(functionId), dest, Const(samplerIndex) };
131  
132                      node.List.AddAfter(node, new Operation(Instruction.Call, 0, unscaledSize, callArgs));
133                  }
134              }
135  
136              return node;
137          }
138  
139          private static LinkedListNode<INode> InsertCoordNormalization(
140              HelperFunctionManager hfm,
141              LinkedListNode<INode> node,
142              ResourceManager resourceManager,
143              IGpuAccessor gpuAccessor,
144              ShaderStage stage)
145          {
146              // Emulate non-normalized coordinates by normalizing the coordinates on the shader.
147              // Without normalization, the coordinates are expected to the in the [0, W or H] range,
148              // and otherwise, it is expected to be in the [0, 1] range.
149              // We normalize by dividing the coords by the texture size.
150  
151              TextureOperation texOp = (TextureOperation)node.Value;
152  
153              bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
154              bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
155  
156              if (isBindless || isIndexed || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle))
157              {
158                  return node;
159              }
160  
161              bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
162  
163              bool isCoordNormalized = gpuAccessor.QueryTextureCoordNormalized(handle, cbufSlot);
164  
165              if (isCoordNormalized || intCoords)
166              {
167                  return node;
168              }
169  
170              int coordsCount = texOp.Type.GetDimensions();
171  
172              int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
173  
174              for (int index = 0; index < normCoordsCount; index++)
175              {
176                  Operand coordSize = Local();
177  
178                  Operand[] texSizeSources = new Operand[] { Const(0) };
179  
180                  LinkedListNode<INode> textureSizeNode = node.List.AddBefore(node, new TextureOperation(
181                      Instruction.TextureQuerySize,
182                      texOp.Type,
183                      texOp.Format,
184                      texOp.Flags,
185                      texOp.Set,
186                      texOp.Binding,
187                      index,
188                      new[] { coordSize },
189                      texSizeSources));
190  
191                  resourceManager.SetUsageFlagsForTextureQuery(texOp.Binding, texOp.Type);
192  
193                  Operand source = texOp.GetSource(index);
194  
195                  Operand coordNormalized = Local();
196  
197                  node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize)));
198  
199                  texOp.SetSource(index, coordNormalized);
200  
201                  InsertTextureSizeUnscale(hfm, textureSizeNode, resourceManager, stage);
202              }
203  
204              return node;
205          }
206  
207          private static LinkedListNode<INode> InsertCoordGatherBias(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
208          {
209              // The gather behavior when the coordinate sits right in the middle of two texels is not well defined.
210              // To ensure the correct texel is sampled, we add a small bias value to the coordinate.
211              // This value is calculated as the minimum value required to change the texel it will sample from,
212              // and is 0 if the host does not require the bias.
213  
214              TextureOperation texOp = (TextureOperation)node.Value;
215  
216              bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
217              bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
218  
219              int gatherBiasPrecision = gpuAccessor.QueryHostGatherBiasPrecision();
220  
221              if (!isGather || gatherBiasPrecision == 0)
222              {
223                  return node;
224              }
225  
226              bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
227  
228              int coordsCount = texOp.Type.GetDimensions();
229              int coordsIndex = isBindless || isIndexed ? 1 : 0;
230  
231              int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
232  
233              for (int index = 0; index < normCoordsCount; index++)
234              {
235                  Operand coordSize = Local();
236                  Operand scaledSize = Local();
237                  Operand bias = Local();
238  
239                  Operand[] texSizeSources;
240  
241                  if (isBindless || isIndexed)
242                  {
243                      texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) };
244                  }
245                  else
246                  {
247                      texSizeSources = new Operand[] { Const(0) };
248                  }
249  
250                  node.List.AddBefore(node, new TextureOperation(
251                      Instruction.TextureQuerySize,
252                      texOp.Type,
253                      texOp.Format,
254                      texOp.Flags,
255                      texOp.Set,
256                      texOp.Binding,
257                      index,
258                      new[] { coordSize },
259                      texSizeSources));
260  
261                  node.List.AddBefore(node, new Operation(
262                      Instruction.FP32 | Instruction.Multiply,
263                      scaledSize,
264                      GenerateI2f(node, coordSize),
265                      ConstF((float)(1 << (gatherBiasPrecision + 1)))));
266                  node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, bias, ConstF(1f), scaledSize));
267  
268                  Operand source = texOp.GetSource(coordsIndex + index);
269  
270                  Operand coordBiased = Local();
271  
272                  node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordBiased, source, bias));
273  
274                  texOp.SetSource(coordsIndex + index, coordBiased);
275              }
276  
277              return node;
278          }
279  
280          private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor, ShaderStage stage)
281          {
282              // Non-constant texture offsets are not allowed (according to the spec),
283              // however some GPUs does support that.
284              // For GPUs where it is not supported, we can replace the instruction with the following:
285              // For texture*Offset, we replace it by texture*, and add the offset to the P coords.
286              // The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords).
287              // For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly.
288              // For textureGatherOffset, we split the operation into up to 4 operations, one for each component
289              // that is accessed, where each textureGather operation has a different offset for each pixel.
290  
291              TextureOperation texOp = (TextureOperation)node.Value;
292  
293              bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
294              bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
295  
296              bool needsOffsetsEmulation = hasOffsets && !gpuAccessor.QueryHostSupportsTextureGatherOffsets();
297  
298              bool hasInvalidOffset = needsOffsetsEmulation || ((hasOffset || hasOffsets) && !gpuAccessor.QueryHostSupportsNonConstantTextureOffset());
299  
300              bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
301  
302              if (!hasInvalidOffset)
303              {
304                  return node;
305              }
306  
307              bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
308              bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0;
309              bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
310              bool hasLodBias = (texOp.Flags & TextureFlags.LodBias) != 0;
311              bool hasLodLevel = (texOp.Flags & TextureFlags.LodLevel) != 0;
312  
313              bool isArray = (texOp.Type & SamplerType.Array) != 0;
314              bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
315              bool isShadow = (texOp.Type & SamplerType.Shadow) != 0;
316  
317              int coordsCount = texOp.Type.GetDimensions();
318  
319              int offsetsCount;
320  
321              if (hasOffsets)
322              {
323                  offsetsCount = coordsCount * 4;
324              }
325              else if (hasOffset)
326              {
327                  offsetsCount = coordsCount;
328              }
329              else
330              {
331                  offsetsCount = 0;
332              }
333  
334              bool isIndexed = resourceManager.IsArrayOfTexturesOrImages(texOp.Binding, isImage: false);
335  
336              Operand[] offsets = new Operand[offsetsCount];
337              Operand[] sources = new Operand[texOp.SourcesCount - offsetsCount];
338  
339              int copyCount = 0;
340  
341              if (isBindless || isIndexed)
342              {
343                  copyCount++;
344              }
345  
346              Operand[] lodSources = new Operand[copyCount + coordsCount];
347  
348              for (int index = 0; index < lodSources.Length; index++)
349              {
350                  lodSources[index] = texOp.GetSource(index);
351              }
352  
353              copyCount += coordsCount;
354  
355              if (isArray)
356              {
357                  copyCount++;
358              }
359  
360              if (isShadow)
361              {
362                  copyCount++;
363              }
364  
365              if (hasDerivatives)
366              {
367                  copyCount += coordsCount * 2;
368              }
369  
370              if (isMultisample)
371              {
372                  copyCount++;
373              }
374              else if (hasLodLevel)
375              {
376                  copyCount++;
377              }
378  
379              int srcIndex = 0;
380              int dstIndex = 0;
381  
382              for (int index = 0; index < copyCount; index++)
383              {
384                  sources[dstIndex++] = texOp.GetSource(srcIndex++);
385              }
386  
387              bool areAllOffsetsConstant = true;
388  
389              for (int index = 0; index < offsetsCount; index++)
390              {
391                  Operand offset = texOp.GetSource(srcIndex++);
392  
393                  areAllOffsetsConstant &= offset.Type == OperandType.Constant;
394  
395                  offsets[index] = offset;
396              }
397  
398              if (!needsOffsetsEmulation)
399              {
400                  hasInvalidOffset &= !areAllOffsetsConstant;
401  
402                  if (!hasInvalidOffset)
403                  {
404                      return node;
405                  }
406              }
407  
408              if (hasLodBias)
409              {
410                  sources[dstIndex++] = texOp.GetSource(srcIndex++);
411              }
412  
413              if (isGather && !isShadow)
414              {
415                  sources[dstIndex++] = texOp.GetSource(srcIndex++);
416              }
417  
418              int coordsIndex = isBindless || isIndexed ? 1 : 0;
419  
420              int componentIndex = texOp.Index;
421  
422              Operand[] dests = new Operand[texOp.DestsCount];
423  
424              for (int i = 0; i < texOp.DestsCount; i++)
425              {
426                  dests[i] = texOp.GetDest(i);
427              }
428  
429              Operand bindlessHandle = isBindless || isIndexed ? sources[0] : null;
430  
431              LinkedListNode<INode> oldNode = node;
432  
433              if (isGather && !isShadow && hasOffsets)
434              {
435                  Operand[] newSources = new Operand[sources.Length];
436  
437                  sources.CopyTo(newSources, 0);
438  
439                  Operand[] texSizes = InsertTextureBaseSize(node, texOp, bindlessHandle, coordsCount);
440  
441                  int destIndex = 0;
442  
443                  for (int compIndex = 0; compIndex < 4; compIndex++)
444                  {
445                      if (((texOp.Index >> compIndex) & 1) == 0)
446                      {
447                          continue;
448                      }
449  
450                      for (int index = 0; index < coordsCount; index++)
451                      {
452                          Operand offset = Local();
453  
454                          Operand intOffset = offsets[index + compIndex * coordsCount];
455  
456                          node.List.AddBefore(node, new Operation(
457                              Instruction.FP32 | Instruction.Divide,
458                              offset,
459                              GenerateI2f(node, intOffset),
460                              GenerateI2f(node, texSizes[index])));
461  
462                          Operand source = sources[coordsIndex + index];
463  
464                          Operand coordPlusOffset = Local();
465  
466                          node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordPlusOffset, source, offset));
467  
468                          newSources[coordsIndex + index] = coordPlusOffset;
469                      }
470  
471                      TextureOperation newTexOp = new(
472                          Instruction.TextureSample,
473                          texOp.Type,
474                          texOp.Format,
475                          texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
476                          texOp.Set,
477                          texOp.Binding,
478                          1 << 3, // W component: i=0, j=0
479                          new[] { dests[destIndex++] },
480                          newSources);
481  
482                      node = node.List.AddBefore(node, newTexOp);
483                  }
484              }
485              else
486              {
487                  if (intCoords)
488                  {
489                      for (int index = 0; index < coordsCount; index++)
490                      {
491                          Operand source = sources[coordsIndex + index];
492  
493                          Operand coordPlusOffset = Local();
494  
495                          node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index]));
496  
497                          sources[coordsIndex + index] = coordPlusOffset;
498                      }
499                  }
500                  else
501                  {
502                      Operand[] texSizes = isGather
503                          ? InsertTextureBaseSize(node, texOp, bindlessHandle, coordsCount)
504                          : InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage);
505  
506                      for (int index = 0; index < coordsCount; index++)
507                      {
508                          Operand offset = Local();
509  
510                          Operand intOffset = offsets[index];
511  
512                          node.List.AddBefore(node, new Operation(
513                              Instruction.FP32 | Instruction.Divide,
514                              offset,
515                              GenerateI2f(node, intOffset),
516                              GenerateI2f(node, texSizes[index])));
517  
518                          Operand source = sources[coordsIndex + index];
519  
520                          Operand coordPlusOffset = Local();
521  
522                          node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordPlusOffset, source, offset));
523  
524                          sources[coordsIndex + index] = coordPlusOffset;
525                      }
526                  }
527  
528                  TextureOperation newTexOp = new(
529                      Instruction.TextureSample,
530                      texOp.Type,
531                      texOp.Format,
532                      texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
533                      texOp.Set,
534                      texOp.Binding,
535                      componentIndex,
536                      dests,
537                      sources);
538  
539                  node = node.List.AddBefore(node, newTexOp);
540              }
541  
542              node.List.Remove(oldNode);
543  
544              for (int index = 0; index < texOp.SourcesCount; index++)
545              {
546                  texOp.SetSource(index, null);
547              }
548  
549              return node;
550          }
551  
552          private static Operand[] InsertTextureBaseSize(
553              LinkedListNode<INode> node,
554              TextureOperation texOp,
555              Operand bindlessHandle,
556              int coordsCount)
557          {
558              Operand[] texSizes = new Operand[coordsCount];
559  
560              for (int index = 0; index < coordsCount; index++)
561              {
562                  texSizes[index] = Local();
563  
564                  Operand[] texSizeSources;
565  
566                  if (bindlessHandle != null)
567                  {
568                      texSizeSources = new Operand[] { bindlessHandle, Const(0) };
569                  }
570                  else
571                  {
572                      texSizeSources = new Operand[] { Const(0) };
573                  }
574  
575                  node.List.AddBefore(node, new TextureOperation(
576                      Instruction.TextureQuerySize,
577                      texOp.Type,
578                      texOp.Format,
579                      texOp.Flags,
580                      texOp.Set,
581                      texOp.Binding,
582                      index,
583                      new[] { texSizes[index] },
584                      texSizeSources));
585              }
586  
587              return texSizes;
588          }
589  
590          private static Operand[] InsertTextureLod(
591              LinkedListNode<INode> node,
592              TextureOperation texOp,
593              Operand[] lodSources,
594              Operand bindlessHandle,
595              int coordsCount,
596              ShaderStage stage)
597          {
598              Operand[] texSizes = new Operand[coordsCount];
599  
600              Operand lod;
601  
602              if (stage == ShaderStage.Fragment)
603              {
604                  lod = Local();
605  
606                  node.List.AddBefore(node, new TextureOperation(
607                      Instruction.Lod,
608                      texOp.Type,
609                      texOp.Format,
610                      texOp.Flags,
611                      texOp.Set,
612                      texOp.Binding,
613                      0,
614                      new[] { lod },
615                      lodSources));
616              }
617              else
618              {
619                  lod = Const(0);
620              }
621  
622              for (int index = 0; index < coordsCount; index++)
623              {
624                  texSizes[index] = Local();
625  
626                  Operand[] texSizeSources;
627  
628                  if (bindlessHandle != null)
629                  {
630                      texSizeSources = new Operand[] { bindlessHandle, GenerateF2i(node, lod) };
631                  }
632                  else
633                  {
634                      texSizeSources = new Operand[] { GenerateF2i(node, lod) };
635                  }
636  
637                  node.List.AddBefore(node, new TextureOperation(
638                      Instruction.TextureQuerySize,
639                      texOp.Type,
640                      texOp.Format,
641                      texOp.Flags,
642                      texOp.Set,
643                      texOp.Binding,
644                      index,
645                      new[] { texSizes[index] },
646                      texSizeSources));
647              }
648  
649              return texSizes;
650          }
651  
652          private static LinkedListNode<INode> InsertSnormNormalization(LinkedListNode<INode> node, ResourceManager resourceManager, IGpuAccessor gpuAccessor)
653          {
654              TextureOperation texOp = (TextureOperation)node.Value;
655  
656              // We can't query the format of a bindless texture,
657              // because the handle is unknown, it can have any format.
658              if (texOp.Flags.HasFlag(TextureFlags.Bindless) || !resourceManager.TryGetCbufSlotAndHandleForTexture(texOp.Binding, out int cbufSlot, out int handle))
659              {
660                  return node;
661              }
662  
663              TextureFormat format = gpuAccessor.QueryTextureFormat(handle, cbufSlot);
664  
665              int maxPositive = format switch
666              {
667                  TextureFormat.R8Snorm => sbyte.MaxValue,
668                  TextureFormat.R8G8Snorm => sbyte.MaxValue,
669                  TextureFormat.R8G8B8A8Snorm => sbyte.MaxValue,
670                  TextureFormat.R16Snorm => short.MaxValue,
671                  TextureFormat.R16G16Snorm => short.MaxValue,
672                  TextureFormat.R16G16B16A16Snorm => short.MaxValue,
673                  _ => 0,
674              };
675  
676              // The value being 0 means that the format is not a SNORM format,
677              // so there's nothing to do here.
678              if (maxPositive == 0)
679              {
680                  return node;
681              }
682  
683              // Do normalization. We assume SINT formats are being used
684              // as replacement for SNORM (which is not supported).
685              for (int i = 0; i < texOp.DestsCount; i++)
686              {
687                  Operand dest = texOp.GetDest(i);
688  
689                  INode[] uses = dest.UseOps.ToArray();
690  
691                  Operation convOp = new(Instruction.ConvertS32ToFP32, Local(), dest);
692                  Operation normOp = new(Instruction.FP32 | Instruction.Multiply, Local(), convOp.Dest, ConstF(1f / maxPositive));
693  
694                  node = node.List.AddAfter(node, convOp);
695                  node = node.List.AddAfter(node, normOp);
696  
697                  foreach (INode useOp in uses)
698                  {
699                      if (useOp is not Operation op)
700                      {
701                          continue;
702                      }
703  
704                      // Replace all uses of the texture pixel value with the normalized value.
705                      for (int index = 0; index < op.SourcesCount; index++)
706                      {
707                          if (op.GetSource(index) == dest)
708                          {
709                              op.SetSource(index, normOp.Dest);
710                          }
711                      }
712                  }
713              }
714  
715              return node;
716          }
717  
718          private static Operand GenerateI2f(LinkedListNode<INode> node, Operand value)
719          {
720              Operand res = Local();
721  
722              node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP32, res, value));
723  
724              return res;
725          }
726  
727          private static Operand GenerateF2i(LinkedListNode<INode> node, Operand value)
728          {
729              Operand res = Local();
730  
731              node.List.AddBefore(node, new Operation(Instruction.ConvertFP32ToS32, res, value));
732  
733              return res;
734          }
735  
736          private static bool IsImageInstructionWithScale(Instruction inst)
737          {
738              // Currently, we don't support scaling images that are modified,
739              // so we only need to care about the load instruction.
740              return inst == Instruction.ImageLoad;
741          }
742  
743          private static bool TypeSupportsScale(SamplerType type)
744          {
745              return (type & SamplerType.Mask) == SamplerType.Texture2D;
746          }
747      }
748  }