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 }