[spirv-reader][ir] Add image sample explicit lod support.

Add support for `OpImageSampleExplicitLod`.

Bug: 407382643
Change-Id: Icafb17c1a045900bcfb0974c963a8025961ee149
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/242574
Auto-Submit: dan sinclair <dsinclair@chromium.org>
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/spirv/reader/lower/builtins.cc b/src/tint/lang/spirv/reader/lower/builtins.cc
index 4b17c86..4c97a4d 100644
--- a/src/tint/lang/spirv/reader/lower/builtins.cc
+++ b/src/tint/lang/spirv/reader/lower/builtins.cc
@@ -241,6 +241,7 @@
                 case spirv::BuiltinFn::kImageQuerySamples:
                 case spirv::BuiltinFn::kImageQuerySize:
                 case spirv::BuiltinFn::kImageQuerySizeLod:
+                case spirv::BuiltinFn::kImageSampleExplicitLod:
                 case spirv::BuiltinFn::kImageSampleImplicitLod:
                 case spirv::BuiltinFn::kImageWrite:
                     // Ignore image methods, they'll be handled by the `Texture` transform.
diff --git a/src/tint/lang/spirv/reader/lower/texture.cc b/src/tint/lang/spirv/reader/lower/texture.cc
index e55efc8..20cbf35 100644
--- a/src/tint/lang/spirv/reader/lower/texture.cc
+++ b/src/tint/lang/spirv/reader/lower/texture.cc
@@ -108,6 +108,7 @@
                     case spirv::BuiltinFn::kImageQuerySamples:
                     case spirv::BuiltinFn::kImageQuerySize:
                     case spirv::BuiltinFn::kImageQuerySizeLod:
+                    case spirv::BuiltinFn::kImageSampleExplicitLod:
                     case spirv::BuiltinFn::kImageSampleImplicitLod:
                     case spirv::BuiltinFn::kImageWrite:
                         builtin_worklist.Push(builtin);
@@ -136,8 +137,9 @@
                 case spirv::BuiltinFn::kImageQuerySizeLod:
                     ImageQuerySize(builtin);
                     break;
+                case spirv::BuiltinFn::kImageSampleExplicitLod:
                 case spirv::BuiltinFn::kImageSampleImplicitLod:
-                    ImageSampleImplicitLod(builtin);
+                    ImageSample(builtin);
                     break;
                 case spirv::BuiltinFn::kImageWrite:
                     ImageWrite(builtin);
@@ -211,6 +213,12 @@
     bool HasBias(uint32_t mask) {
         return (mask & static_cast<uint32_t>(ImageOperandsMask::kBias)) != 0;
     }
+    bool HasGrad(uint32_t mask) {
+        return (mask & static_cast<uint32_t>(ImageOperandsMask::kGrad)) != 0;
+    }
+    bool HasLod(uint32_t mask) {
+        return (mask & static_cast<uint32_t>(ImageOperandsMask::kLod)) != 0;
+    }
     bool HasConstOffset(uint32_t mask) {
         return (mask & static_cast<uint32_t>(ImageOperandsMask::kConstOffset)) != 0;
     }
@@ -246,7 +254,7 @@
         call->Destroy();
     }
 
-    void ImageSampleImplicitLod(spirv::ir::BuiltinCall* call) {
+    void ImageSample(spirv::ir::BuiltinCall* call) {
         const auto& args = call->Args();
 
         auto* sampled_image = args[0];
@@ -255,6 +263,8 @@
         core::ir::Value* sampler = nullptr;
         std::tie(tex, sampler) = GetTextureSampler(sampled_image);
 
+        auto* tex_ty = tex->Type();
+
         auto* coords = args[1];
         uint32_t operand_mask = GetOperandMask(args[2]);
 
@@ -264,18 +274,51 @@
             new_args.Push(tex);
             new_args.Push(sampler);
 
-            ProcessCoords(tex->Type(), coords, new_args);
+            ProcessCoords(tex_ty, coords, new_args);
 
             core::BuiltinFn fn = core::BuiltinFn::kTextureSample;
             if (HasBias(operand_mask)) {
                 fn = core::BuiltinFn::kTextureSampleBias;
                 new_args.Push(args[idx++]);
             }
+            if (HasLod(operand_mask)) {
+                fn = core::BuiltinFn::kTextureSampleLevel;
+
+                core::ir::Value* lod = args[idx++];
+
+                // Depth texture LOD in WGSL is i32/u32 but f32 in SPIR-V.
+                // Convert to i32
+                if (tex_ty->Is<core::type::DepthTexture>()) {
+                    lod = b.Convert(ty.i32(), lod)->Result();
+                }
+                new_args.Push(lod);
+            }
+            if (HasGrad(operand_mask)) {
+                fn = core::BuiltinFn::kTextureSampleGrad;
+                new_args.Push(args[idx++]);  // ddx
+                new_args.Push(args[idx++]);  // ddy
+            }
             if (HasConstOffset(operand_mask)) {
                 ProcessOffset(args[idx++], new_args);
             }
 
-            b.CallWithResult(call->DetachResult(), fn, new_args);
+            // Depth textures have a single value return in WGSL, but a vec4 in SPIR-V.
+            auto* call_ty = call->Result()->Type();
+            if (tex_ty->IsAnyOf<core::type::DepthTexture, core::type::DepthMultisampledTexture>()) {
+                call_ty = call_ty->DeepestElement();
+            }
+            auto* res = b.Call(call_ty, fn, new_args)->Result();
+
+            // Restore the vec4 result by padding with 0's.
+            if (call_ty != call->Result()->Type()) {
+                auto* vec = call->Result()->Type()->As<core::type::Vector>();
+                TINT_ASSERT(vec && vec->Width() == 4);
+
+                auto* z = b.Zero(call_ty);
+                res = b.Construct(call->Result()->Type(), res, z, z, z)->Result();
+            }
+
+            call->Result()->ReplaceAllUsesWith(res);
         });
         call->Destroy();
     }
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 7f7f38d..2c7669c 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -1408,8 +1408,11 @@
                 case spv::Op::OpImageQuerySizeLod:
                     EmitImageQuerySizeLod(inst);
                     break;
+                case spv::Op::OpImageSampleExplicitLod:
+                    EmitImageSample(inst, spirv::BuiltinFn::kImageSampleExplicitLod);
+                    break;
                 case spv::Op::OpImageSampleImplicitLod:
-                    EmitImageSampleImplicitLod(inst);
+                    EmitImageSample(inst, spirv::BuiltinFn::kImageSampleImplicitLod);
                     break;
                 case spv::Op::OpImageWrite:
                     EmitImageWrite(inst);
@@ -1473,7 +1476,7 @@
              inst.result_id());
     }
 
-    void EmitImageSampleImplicitLod(const spvtools::opt::Instruction& inst) {
+    void EmitImageSample(const spvtools::opt::Instruction& inst, spirv::BuiltinFn fn) {
         auto sampled_image = Value(inst.GetSingleWordInOperand(0));
         auto* coord = Value(inst.GetSingleWordInOperand(1));
 
@@ -1494,9 +1497,7 @@
             args.Push(b_.Zero(ty_.u32()));
         }
 
-        Emit(b_.Call<spirv::ir::BuiltinCall>(Type(inst.type_id()),
-                                             spirv::BuiltinFn::kImageSampleImplicitLod, args),
-             inst.result_id());
+        Emit(b_.Call<spirv::ir::BuiltinCall>(Type(inst.type_id()), fn, args), inst.result_id());
     }
 
     void EmitImageWrite(const spvtools::opt::Instruction& inst) {
diff --git a/src/tint/lang/spirv/reader/texture_test.cc b/src/tint/lang/spirv/reader/texture_test.cc
index d763946..fe68abb 100644
--- a/src/tint/lang/spirv/reader/texture_test.cc
+++ b/src/tint/lang/spirv/reader/texture_test.cc
@@ -362,6 +362,71 @@
 )");
 }
 
+TEST_F(SpirvReaderTest, ImageSampleExplicitLod) {
+    EXPECT_IR(R"(
+           OpCapability Shader
+           OpCapability Sampled1D
+           OpMemoryModel Logical Simple
+           OpEntryPoint Fragment %main "main"
+           OpExecutionMode %main OriginUpperLeft
+           OpName %10 "wg"
+           OpDecorate %var_sampler DescriptorSet 0
+           OpDecorate %var_sampler Binding 0
+           OpDecorate %10 DescriptorSet 2
+           OpDecorate %10 Binding 0
+    %int = OpTypeInt 32 1
+  %float = OpTypeFloat 32
+  %v2int = OpTypeVector %int 2
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+    %tex = OpTypeImage %float 2D 0 0 0 1 Unknown
+%ptr_tex = OpTypePointer UniformConstant %tex
+%sampled_img = OpTypeSampledImage %tex
+%sampler = OpTypeSampler
+%ptr_sampler = OpTypePointer UniformConstant %sampler
+   %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+
+  %int_1 = OpConstant %int 1
+%float_1 = OpConstant %float 1
+%float_2 = OpConstant %float 2
+%coords2 = OpConstantComposite %v2float %float_1 %float_2
+
+%var_sampler = OpVariable %ptr_sampler UniformConstant
+     %10 = OpVariable %ptr_tex UniformConstant
+
+ %int_10 = OpConstant %int 10
+ %int_11 = OpConstant %int 11
+%offset2i = OpConstantComposite %v2int %int_10 %int_11
+
+   %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+    %sam = OpLoad %sampler %var_sampler
+     %im = OpLoad %tex %10
+%sampled_image = OpSampledImage %sampled_img %im %sam
+ %result = OpImageSampleExplicitLod %v4float %sampled_image %coords2 Lod|ConstOffset %float_1 %offset2i
+     %r2 = OpFAdd %v4float %result %result
+           OpReturn
+           OpFunctionEnd
+        )",
+              R"(
+$B1: {  # root
+  %1:ptr<handle, sampler, read> = var undef @binding_point(0, 0)
+  %wg:ptr<handle, texture_2d<f32>, read> = var undef @binding_point(2, 0)
+}
+
+%main = @fragment func():void {
+  $B2: {
+    %4:sampler = load %1
+    %5:texture_2d<f32> = load %wg
+    %6:vec4<f32> = textureSampleLevel %5, %4, vec2<f32>(1.0f, 2.0f), 1.0f, vec2<i32>(10i, 11i)
+    %7:vec4<f32> = add %6, %6
+    ret
+  }
+}
+)");
+}
+
 TEST_F(SpirvReaderTest, ImageQuerySize) {
     EXPECT_IR(R"(
            OpCapability Shader
@@ -1060,7 +1125,7 @@
         ));
 
 INSTANTIATE_TEST_SUITE_P(
-    DISABLED_SpirvReaderTest_ImageSampleExplicitLod,
+    SpirvReaderTest_ImageSampleExplicitLod,
     SamplerTest,
     ::testing::Values(
         ImgData{
@@ -1068,14 +1133,19 @@
             .spirv_type = "%float 2D 0 0 0 1 Unknown",
             .spirv_fn = "OpImageSampleExplicitLod %v4float %sampled_image %coords2 Lod %float_null",
             .wgsl_type = "texture_2d<f32>",
-            .wgsl_fn = "textureSampleLevel %4, %5, vec2<f32>(1.0f, 2.0f), 0.0f",
+            .wgsl_fn = R"(
+    %6:vec4<f32> = textureSampleLevel %5, %4, vec2<f32>(1.0f, 2.0f), 0.0f)",
         },
         ImgData{
             .name = "2D Array Lod",
             .spirv_type = "%float 2D 0 1 0 1 Unknown",
             .spirv_fn = "OpImageSampleExplicitLod %v4float %sampled_image %coords3 Lod %float_null",
             .wgsl_type = "texture_2d_array<f32>",
-            .wgsl_fn = "textureSampleLevel %4, %5, vec2<f32>(1.0f, 2.0f), 3i, 0.0f",
+            .wgsl_fn = R"(
+    %6:vec2<f32> = swizzle vec3<f32>(1.0f, 2.0f, 3.0f), xy
+    %7:f32 = swizzle vec3<f32>(1.0f, 2.0f, 3.0f), z
+    %8:i32 = convert %7
+    %9:vec4<f32> = textureSampleLevel %5, %4, %6, %8, 0.0f)",
         },
         ImgData{
             .name = "2D Lod ConstOffset signed",
@@ -1083,8 +1153,8 @@
             .spirv_fn = "OpImageSampleExplicitLod %v4float %sampled_image %coords2 Lod|ConstOffset "
                         "%float_null %offset2i",
             .wgsl_type = "texture_2d<f32>",
-            .wgsl_fn =
-                "textureSampleLevel %4, %5, vec2<f32>(1.0f, 2.0f), 0.0f, vec2<i32>(10i, 11i)",
+            .wgsl_fn = R"(
+    %6:vec4<f32> = textureSampleLevel %5, %4, vec2<f32>(1.0f, 2.0f), 0.0f, vec2<i32>(10i, 11i))",
         },
         ImgData{
             .name = "2D lod ConstOffset unsigned",
@@ -1092,8 +1162,9 @@
             .spirv_fn = "OpImageSampleExplicitLod %v4float %sampled_image %coords2 Lod|ConstOffset "
                         "%float_null %offset2u",
             .wgsl_type = "texture_2d<f32>",
-            .wgsl_fn =
-                "textureSampleLevel %4, %5, vec2<f32>(1.0f, 2.0f), 0.0f, vec2<u32>(20u, 21u)",
+            .wgsl_fn = R"(
+    %6:vec2<i32> = convert vec2<u32>(20u, 21u)
+    %7:vec4<f32> = textureSampleLevel %5, %4, vec2<f32>(1.0f, 2.0f), 0.0f, %6)",
         },
         ImgData{
             .name = "2D Array lod ConstOffset",
@@ -1101,17 +1172,20 @@
             .spirv_fn = "OpImageSampleExplicitLod %v4float %sampled_image %coords3 Lod|ConstOffset "
                         "%float_null %offset2i",
             .wgsl_type = "texture_2d_array<f32>",
-            .wgsl_fn =
-                "textureSampleLevel %4, %5, vec2<f32>(1.0f, 2.0f), 3i, 0.0f, vec2<i32>(10i, 11i)",
+            .wgsl_fn = R"(
+    %6:vec2<f32> = swizzle vec3<f32>(1.0f, 2.0f, 3.0f), xy
+    %7:f32 = swizzle vec3<f32>(1.0f, 2.0f, 3.0f), z
+    %8:i32 = convert %7
+    %9:vec4<f32> = textureSampleLevel %5, %4, %6, %8, 0.0f, vec2<i32>(10i, 11i))",
         },
         ImgData{
             .name = "2D Grad",
             .spirv_type = "%float 2D 0 0 0 1 Unknown",
             .spirv_fn =
-                "OpImageSampleExplicitLod %v4float %sampled_image %coords12 Grad %vf12 %vf21",
+                "OpImageSampleExplicitLod %v4float %sampled_image %coords2 Grad %vf12 %vf21",
             .wgsl_type = "texture_2d<f32>",
-            .wgsl_fn = "textureSampleGrad %4, %5, vec2<f32>(1.0f, 2.0f), vec2<f32>(1.0f, 2.0f), "
-                       "vec2<f32>(2.0f, 1.0)",
+            .wgsl_fn = R"(
+    %6:vec4<f32> = textureSampleGrad %5, %4, vec2<f32>(1.0f, 2.0f), vec2<f32>(1.0f, 2.0f), vec2<f32>(2.0f, 1.0f))",
         },
         ImgData{
             .name = "2D Array Grad",
@@ -1119,8 +1193,11 @@
             .spirv_fn =
                 "OpImageSampleExplicitLod %v4float %sampled_image %coords3 Grad %vf12 %vf21",
             .wgsl_type = "texture_2d_array<f32>",
-            .wgsl_fn = "textureSampleGrad %4, %5, vec2<f32>(1.0f, 2.0f), 3i, vec2<f32>(1.0f, "
-                       "2.0f), vec2<f32>(2.0f, 1.0f)",
+            .wgsl_fn = R"(
+    %6:vec2<f32> = swizzle vec3<f32>(1.0f, 2.0f, 3.0f), xy
+    %7:f32 = swizzle vec3<f32>(1.0f, 2.0f, 3.0f), z
+    %8:i32 = convert %7
+    %9:vec4<f32> = textureSampleGrad %5, %4, %6, %8, vec2<f32>(1.0f, 2.0f), vec2<f32>(2.0f, 1.0f))",
         },
         ImgData{
             .name = "2D Grad ConstOffset signed",
@@ -1128,8 +1205,8 @@
             .spirv_fn = "OpImageSampleExplicitLod %v4float %sampled_image %coords2 "
                         "Grad|ConstOffset %vf12 %vf21 %offset2i",
             .wgsl_type = "texture_2d<f32>",
-            .wgsl_fn = "textureSampleGrad %4, %5, vec2<f32>(1.0f, 2.0f), vec2<f32>(1.0f, 2.0f), "
-                       "vec2<f32>(2.0f, 1.0), vec2<i32>(10i, 11i)",
+            .wgsl_fn = R"(
+    %6:vec4<f32> = textureSampleGrad %5, %4, vec2<f32>(1.0f, 2.0f), vec2<f32>(1.0f, 2.0f), vec2<f32>(2.0f, 1.0f), vec2<i32>(10i, 11i))",
         },
         ImgData{
             .name = "2D Grad ConstOffset Unsigned",
@@ -1137,33 +1214,44 @@
             .spirv_fn = "OpImageSampleExplicitLod %v4float %sampled_image %coords2 "
                         "Grad|ConstOffset %vf12 %vf21 %offset2u",
             .wgsl_type = "texture_2d<f32>",
-            .wgsl_fn = "textureSampleGrad %4, %5, vec2<f32>(1.0f, 2.0f), vec2<f32>(1.0f, 2.0f), "
-                       "vec2<f32>(2.0f, 1.0f), vec2<u32>(20u, 21u)",
+            .wgsl_fn = R"(
+    %6:vec2<i32> = convert vec2<u32>(20u, 21u)
+    %7:vec4<f32> = textureSampleGrad %5, %4, vec2<f32>(1.0f, 2.0f), vec2<f32>(1.0f, 2.0f), vec2<f32>(2.0f, 1.0f), %6)",
         },
         ImgData{
-            .name = "2D Arrayed Grad ConstOffset",
+            .name = "2D Array Grad ConstOffset",
             .spirv_type = "%float 2D 0 1 0 1 Unknown",
             .spirv_fn = "OpImageSampleExplicitLod %v4float %sampled_image %coords3 "
                         "Grad|ConstOffset %vf12 %vf21 %offset2i",
             .wgsl_type = "texture_2d_array<f32>",
-            .wgsl_fn = "textureSampleGrad %4, %5, vec2<f32>(1.0f, 2.0f), 3i, vec2<f32>(1.0f, "
-                       "2.0f), vec2<f32>(2.0f, 1.0f, vec2<i32>(10i, 11i)",
+            .wgsl_fn = R"(
+    %6:vec2<f32> = swizzle vec3<f32>(1.0f, 2.0f, 3.0f), xy
+    %7:f32 = swizzle vec3<f32>(1.0f, 2.0f, 3.0f), z
+    %8:i32 = convert %7
+    %9:vec4<f32> = textureSampleGrad %5, %4, %6, %8, vec2<f32>(1.0f, 2.0f), vec2<f32>(2.0f, 1.0f), vec2<i32>(10i, 11i))",
         },
         ImgData{
-            .name = "2D arrayed Grad ConstOffset Unsigned",
+            .name = "2D Array Grad ConstOffset Unsigned",
             .spirv_type = "%float 2D 0 1 0 1 Unknown",
             .spirv_fn = "OpImageSampleExplicitLod %v4float %sampled_image %coords3 "
                         "Grad|ConstOffset %vf12 %vf21 %offset2u",
             .wgsl_type = "texture_2d_array<f32>",
-            .wgsl_fn = "textureSampleGrad %4, %5, vec2<f32>(1.0f, 2.0f), 3i, vec2<f32>(1.0f, 2.0), "
-                       "vec2<f32>(2.0f, 1.0f), vec2<u32>(20u, 21u)",
+            .wgsl_fn = R"(
+    %6:vec2<f32> = swizzle vec3<f32>(1.0f, 2.0f, 3.0f), xy
+    %7:f32 = swizzle vec3<f32>(1.0f, 2.0f, 3.0f), z
+    %8:i32 = convert %7
+    %9:vec2<i32> = convert vec2<u32>(20u, 21u)
+    %10:vec4<f32> = textureSampleGrad %5, %4, %6, %8, vec2<f32>(1.0f, 2.0f), vec2<f32>(2.0f, 1.0f), %9)",
         },
         ImgData{
             .name = "2D Depth",
             .spirv_type = "%float 2D 1 0 0 1 Unknown",
             .spirv_fn = "OpImageSampleExplicitLod %v4float %sampled_image %vf12 Lod %float_1",
             .wgsl_type = "texture_depth_2d",
-            .wgsl_fn = "textureSampleLevel %4, %5, vec2<f23>(1.0f, 2.0f), 1i",
+            .wgsl_fn = R"(
+    %6:i32 = convert 1.0f
+    %7:f32 = textureSampleLevel %5, %4, vec2<f32>(1.0f, 2.0f), %6
+    %8:vec4<f32> = construct %7, 0.0f, 0.0f, 0.0f)",
         }));
 
 INSTANTIATE_TEST_SUITE_P(