[ir][spirv-writer] Handle textureGather{Compare}

These map to OpImageGather and OpImageDrefGather. Unlike other texture
builtins, the texture argument is not always first, so needs some
special casing in tests.

Bug: tint:1906
Change-Id: I6046b61073ca6b3f77c50b37501874cdcec7903f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/141902
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Auto-Submit: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/ir/intrinsic_call.cc b/src/tint/ir/intrinsic_call.cc
index 4982e79..5f51bc1 100644
--- a/src/tint/ir/intrinsic_call.cc
+++ b/src/tint/ir/intrinsic_call.cc
@@ -79,6 +79,12 @@
         case IntrinsicCall::Kind::kSpirvImageFetch:
             out << "spirv.image_fetch";
             break;
+        case IntrinsicCall::Kind::kSpirvImageGather:
+            out << "spirv.image_gather";
+            break;
+        case IntrinsicCall::Kind::kSpirvImageDrefGather:
+            out << "spirv.image_dref_gather";
+            break;
         case IntrinsicCall::Kind::kSpirvImageQuerySize:
             out << "spirv.image_query_size";
             break;
diff --git a/src/tint/ir/intrinsic_call.h b/src/tint/ir/intrinsic_call.h
index ed90316..9c10969 100644
--- a/src/tint/ir/intrinsic_call.h
+++ b/src/tint/ir/intrinsic_call.h
@@ -44,6 +44,8 @@
         kSpirvAtomicXor,
         kSpirvDot,
         kSpirvImageFetch,
+        kSpirvImageGather,
+        kSpirvImageDrefGather,
         kSpirvImageQuerySize,
         kSpirvImageQuerySizeLod,
         kSpirvImageSampleImplicitLod,
diff --git a/src/tint/ir/transform/builtin_polyfill_spirv.cc b/src/tint/ir/transform/builtin_polyfill_spirv.cc
index 433c9be..1a12495 100644
--- a/src/tint/ir/transform/builtin_polyfill_spirv.cc
+++ b/src/tint/ir/transform/builtin_polyfill_spirv.cc
@@ -74,6 +74,8 @@
                     case builtin::Function::kDot:
                     case builtin::Function::kSelect:
                     case builtin::Function::kTextureDimensions:
+                    case builtin::Function::kTextureGather:
+                    case builtin::Function::kTextureGatherCompare:
                     case builtin::Function::kTextureLoad:
                     case builtin::Function::kTextureSample:
                     case builtin::Function::kTextureSampleBias:
@@ -116,6 +118,10 @@
                 case builtin::Function::kTextureDimensions:
                     replacement = TextureDimensions(builtin);
                     break;
+                case builtin::Function::kTextureGather:
+                case builtin::Function::kTextureGatherCompare:
+                    replacement = TextureGather(builtin);
+                    break;
                 case builtin::Function::kTextureLoad:
                     replacement = TextureLoad(builtin);
                     break;
@@ -503,6 +509,80 @@
         return result;
     }
 
+    /// Handle a textureGather*() builtin.
+    /// @param builtin the builtin call instruction
+    /// @returns the replacement value
+    Value* TextureGather(CoreBuiltinCall* builtin) {
+        // Helper to get the next argument from the call, or nullptr if there are no more arguments.
+        uint32_t arg_idx = 0;
+        auto next_arg = [&]() {
+            return arg_idx < builtin->Args().Length() ? builtin->Args()[arg_idx++] : nullptr;
+        };
+
+        auto* component = next_arg();
+        if (!component->Type()->is_integer_scalar()) {
+            // The first argument wasn't the component, so it must be the texture instead.
+            // Use constant zero for the component.
+            component = b.Constant(0_u);
+            arg_idx--;
+        }
+        auto* texture = next_arg();
+        auto* sampler = next_arg();
+        auto* coords = next_arg();
+        auto* texture_ty = texture->Type()->As<type::Texture>();
+
+        // Use OpSampledImage to create an OpTypeSampledImage object.
+        auto* sampled_image =
+            b.Call(ty.Get<SampledImage>(texture_ty), IntrinsicCall::Kind::kSpirvSampledImage,
+                   utils::Vector{texture, sampler});
+        sampled_image->InsertBefore(builtin);
+
+        // Append the array index to the coordinates if provided.
+        auto* array_idx = IsTextureArray(texture_ty->dim()) ? next_arg() : nullptr;
+        if (array_idx) {
+            coords = AppendArrayIndex(coords, array_idx, builtin);
+        }
+
+        // Determine which SPIR-V intrinsic to use and which optional image operands are needed.
+        enum IntrinsicCall::Kind intrinsic;
+        Value* depth = nullptr;
+        ImageOperands operands;
+        switch (builtin->Func()) {
+            case builtin::Function::kTextureGather:
+                intrinsic = IntrinsicCall::Kind::kSpirvImageGather;
+                operands.offset = next_arg();
+                break;
+            case builtin::Function::kTextureGatherCompare:
+                intrinsic = IntrinsicCall::Kind::kSpirvImageDrefGather;
+                depth = next_arg();
+                operands.offset = next_arg();
+                break;
+            default:
+                return nullptr;
+        }
+
+        // Start building the argument list for the intrinsic.
+        // The first two operands are always the sampled image and then the coordinates, followed by
+        // either the depth reference or the component.
+        utils::Vector<Value*, 8> intrinsic_args;
+        intrinsic_args.Push(sampled_image->Result());
+        intrinsic_args.Push(coords);
+        if (depth) {
+            intrinsic_args.Push(depth);
+        } else {
+            intrinsic_args.Push(component);
+        }
+
+        // Add the optional image operands, if any.
+        AppendImageOperands(operands, intrinsic_args, builtin, /* requires_float_lod */ true);
+
+        // Call the intrinsic.
+        auto* result_ty = builtin->Result()->Type();
+        auto* texture_call = b.Call(result_ty, intrinsic, std::move(intrinsic_args));
+        texture_call->InsertBefore(builtin);
+        return texture_call->Result();
+    }
+
     /// Handle a textureLoad() builtin.
     /// @param builtin the builtin call instruction
     /// @returns the replacement value
diff --git a/src/tint/ir/transform/builtin_polyfill_spirv_test.cc b/src/tint/ir/transform/builtin_polyfill_spirv_test.cc
index a3a09793..8f26e9d 100644
--- a/src/tint/ir/transform/builtin_polyfill_spirv_test.cc
+++ b/src/tint/ir/transform/builtin_polyfill_spirv_test.cc
@@ -1874,6 +1874,289 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(IR_BuiltinPolyfillSpirvTest, TextureGather_2D) {
+    auto* t =
+        b.FunctionParam("t", ty.Get<type::SampledTexture>(type::TextureDimension::k2d, ty.f32()));
+    auto* s = b.FunctionParam("s", ty.sampler());
+    auto* component = b.FunctionParam("component", ty.i32());
+    auto* coords = b.FunctionParam("coords", ty.vec2<f32>());
+    auto* func = b.Function("foo", ty.vec4<f32>());
+    func->SetParams({component, t, s, coords});
+
+    b.With(func->Block(), [&] {
+        auto* result =
+            b.Call(ty.vec4<f32>(), builtin::Function::kTextureGather, component, t, s, coords);
+        b.Return(func, result);
+    });
+
+    auto* src = R"(
+%foo = func(%component:i32, %t:texture_2d<f32>, %s:sampler, %coords:vec2<f32>):vec4<f32> -> %b1 {
+  %b1 = block {
+    %6:vec4<f32> = textureGather %component, %t, %s, %coords
+    ret %6
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%component:i32, %t:texture_2d<f32>, %s:sampler, %coords:vec2<f32>):vec4<f32> -> %b1 {
+  %b1 = block {
+    %6:spirv.sampled_image = spirv.sampled_image %t, %s
+    %7:vec4<f32> = spirv.image_gather %6, %coords, %component, 0u
+    ret %7
+  }
+}
+)";
+
+    Run<BuiltinPolyfillSpirv>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillSpirvTest, TextureGather_2D_Offset) {
+    auto* t =
+        b.FunctionParam("t", ty.Get<type::SampledTexture>(type::TextureDimension::k2d, ty.f32()));
+    auto* s = b.FunctionParam("s", ty.sampler());
+    auto* component = b.FunctionParam("component", ty.i32());
+    auto* coords = b.FunctionParam("coords", ty.vec2<f32>());
+    auto* func = b.Function("foo", ty.vec4<f32>());
+    func->SetParams({t, s, component, coords});
+
+    b.With(func->Block(), [&] {
+        auto* result = b.Call(
+            ty.vec4<f32>(), builtin::Function::kTextureGather, component, t, s, coords,
+            b.Constant(mod.constant_values.Splat(ty.vec2<i32>(), mod.constant_values.Get(1_i), 2)));
+        b.Return(func, result);
+    });
+
+    auto* src = R"(
+%foo = func(%t:texture_2d<f32>, %s:sampler, %component:i32, %coords:vec2<f32>):vec4<f32> -> %b1 {
+  %b1 = block {
+    %6:vec4<f32> = textureGather %component, %t, %s, %coords, vec2<i32>(1i)
+    ret %6
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%t:texture_2d<f32>, %s:sampler, %component:i32, %coords:vec2<f32>):vec4<f32> -> %b1 {
+  %b1 = block {
+    %6:spirv.sampled_image = spirv.sampled_image %t, %s
+    %7:vec4<f32> = spirv.image_gather %6, %coords, %component, 8u, vec2<i32>(1i)
+    ret %7
+  }
+}
+)";
+
+    Run<BuiltinPolyfillSpirv>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillSpirvTest, TextureGather_2DArray_Offset) {
+    auto* t = b.FunctionParam(
+        "t", ty.Get<type::SampledTexture>(type::TextureDimension::k2dArray, ty.f32()));
+    auto* s = b.FunctionParam("s", ty.sampler());
+    auto* component = b.FunctionParam("component", ty.i32());
+    auto* coords = b.FunctionParam("coords", ty.vec2<f32>());
+    auto* array_idx = b.FunctionParam("array_idx", ty.i32());
+    auto* func = b.Function("foo", ty.vec4<f32>());
+    func->SetParams({t, s, component, coords, array_idx});
+
+    b.With(func->Block(), [&] {
+        auto* result = b.Call(
+            ty.vec4<f32>(), builtin::Function::kTextureGather, component, t, s, coords, array_idx,
+            b.Constant(mod.constant_values.Splat(ty.vec2<i32>(), mod.constant_values.Get(1_i), 2)));
+        b.Return(func, result);
+    });
+
+    auto* src = R"(
+%foo = func(%t:texture_2d_array<f32>, %s:sampler, %component:i32, %coords:vec2<f32>, %array_idx:i32):vec4<f32> -> %b1 {
+  %b1 = block {
+    %7:vec4<f32> = textureGather %component, %t, %s, %coords, %array_idx, vec2<i32>(1i)
+    ret %7
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%t:texture_2d_array<f32>, %s:sampler, %component:i32, %coords:vec2<f32>, %array_idx:i32):vec4<f32> -> %b1 {
+  %b1 = block {
+    %7:spirv.sampled_image = spirv.sampled_image %t, %s
+    %8:f32 = convert %array_idx
+    %9:vec3<f32> = construct %coords, %8
+    %10:vec4<f32> = spirv.image_gather %7, %9, %component, 8u, vec2<i32>(1i)
+    ret %10
+  }
+}
+)";
+
+    Run<BuiltinPolyfillSpirv>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillSpirvTest, TextureGather_Depth2D) {
+    auto* t = b.FunctionParam("t", ty.Get<type::DepthTexture>(type::TextureDimension::k2d));
+    auto* s = b.FunctionParam("s", ty.sampler());
+    auto* coords = b.FunctionParam("coords", ty.vec2<f32>());
+    auto* func = b.Function("foo", ty.vec4<f32>());
+    func->SetParams({t, s, coords});
+
+    b.With(func->Block(), [&] {
+        auto* result = b.Call(ty.vec4<f32>(), builtin::Function::kTextureGather, t, s, coords);
+        b.Return(func, result);
+    });
+
+    auto* src = R"(
+%foo = func(%t:texture_depth_2d, %s:sampler, %coords:vec2<f32>):vec4<f32> -> %b1 {
+  %b1 = block {
+    %5:vec4<f32> = textureGather %t, %s, %coords
+    ret %5
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%t:texture_depth_2d, %s:sampler, %coords:vec2<f32>):vec4<f32> -> %b1 {
+  %b1 = block {
+    %5:spirv.sampled_image = spirv.sampled_image %t, %s
+    %6:vec4<f32> = spirv.image_gather %5, %coords, 0u, 0u
+    ret %6
+  }
+}
+)";
+
+    Run<BuiltinPolyfillSpirv>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillSpirvTest, TextureGatherCompare_Depth2D) {
+    auto* t = b.FunctionParam("t", ty.Get<type::DepthTexture>(type::TextureDimension::k2d));
+    auto* s = b.FunctionParam("s", ty.sampler());
+    auto* coords = b.FunctionParam("coords", ty.vec2<f32>());
+    auto* depth = b.FunctionParam("depth", ty.f32());
+    auto* func = b.Function("foo", ty.vec4<f32>());
+    func->SetParams({t, s, coords, depth});
+
+    b.With(func->Block(), [&] {
+        auto* result =
+            b.Call(ty.vec4<f32>(), builtin::Function::kTextureGatherCompare, t, s, coords, depth);
+        b.Return(func, result);
+    });
+
+    auto* src = R"(
+%foo = func(%t:texture_depth_2d, %s:sampler, %coords:vec2<f32>, %depth:f32):vec4<f32> -> %b1 {
+  %b1 = block {
+    %6:vec4<f32> = textureGatherCompare %t, %s, %coords, %depth
+    ret %6
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%t:texture_depth_2d, %s:sampler, %coords:vec2<f32>, %depth:f32):vec4<f32> -> %b1 {
+  %b1 = block {
+    %6:spirv.sampled_image = spirv.sampled_image %t, %s
+    %7:vec4<f32> = spirv.image_dref_gather %6, %coords, %depth, 0u
+    ret %7
+  }
+}
+)";
+
+    Run<BuiltinPolyfillSpirv>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillSpirvTest, TextureGatherCompare_Depth2D_Offset) {
+    auto* t = b.FunctionParam("t", ty.Get<type::DepthTexture>(type::TextureDimension::k2d));
+    auto* s = b.FunctionParam("s", ty.sampler());
+    auto* coords = b.FunctionParam("coords", ty.vec2<f32>());
+    auto* depth = b.FunctionParam("depth", ty.f32());
+    auto* func = b.Function("foo", ty.vec4<f32>());
+    func->SetParams({t, s, coords, depth});
+
+    b.With(func->Block(), [&] {
+        auto* result = b.Call(
+            ty.vec4<f32>(), builtin::Function::kTextureGatherCompare, t, s, coords, depth,
+            b.Constant(mod.constant_values.Splat(ty.vec2<i32>(), mod.constant_values.Get(1_i), 2)));
+        b.Return(func, result);
+    });
+
+    auto* src = R"(
+%foo = func(%t:texture_depth_2d, %s:sampler, %coords:vec2<f32>, %depth:f32):vec4<f32> -> %b1 {
+  %b1 = block {
+    %6:vec4<f32> = textureGatherCompare %t, %s, %coords, %depth, vec2<i32>(1i)
+    ret %6
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%t:texture_depth_2d, %s:sampler, %coords:vec2<f32>, %depth:f32):vec4<f32> -> %b1 {
+  %b1 = block {
+    %6:spirv.sampled_image = spirv.sampled_image %t, %s
+    %7:vec4<f32> = spirv.image_dref_gather %6, %coords, %depth, 8u, vec2<i32>(1i)
+    ret %7
+  }
+}
+)";
+
+    Run<BuiltinPolyfillSpirv>();
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillSpirvTest, TextureGatherCompare_Depth2DArray) {
+    auto* t = b.FunctionParam("t", ty.Get<type::DepthTexture>(type::TextureDimension::k2dArray));
+    auto* s = b.FunctionParam("s", ty.sampler());
+    auto* coords = b.FunctionParam("coords", ty.vec2<f32>());
+    auto* array_idx = b.FunctionParam("array_idx", ty.u32());
+    auto* depth = b.FunctionParam("depth", ty.f32());
+    auto* func = b.Function("foo", ty.vec4<f32>());
+    func->SetParams({t, s, coords, array_idx, depth});
+
+    b.With(func->Block(), [&] {
+        auto* result = b.Call(ty.vec4<f32>(), builtin::Function::kTextureGatherCompare, t, s,
+                              coords, array_idx, depth);
+        b.Return(func, result);
+    });
+
+    auto* src = R"(
+%foo = func(%t:texture_depth_2d_array, %s:sampler, %coords:vec2<f32>, %array_idx:u32, %depth:f32):vec4<f32> -> %b1 {
+  %b1 = block {
+    %7:vec4<f32> = textureGatherCompare %t, %s, %coords, %array_idx, %depth
+    ret %7
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%t:texture_depth_2d_array, %s:sampler, %coords:vec2<f32>, %array_idx:u32, %depth:f32):vec4<f32> -> %b1 {
+  %b1 = block {
+    %7:spirv.sampled_image = spirv.sampled_image %t, %s
+    %8:f32 = convert %array_idx
+    %9:vec3<f32> = construct %coords, %8
+    %10:vec4<f32> = spirv.image_dref_gather %7, %9, %depth, 0u
+    ret %10
+  }
+}
+)";
+
+    Run<BuiltinPolyfillSpirv>();
+
+    EXPECT_EQ(expect, str());
+}
+
 TEST_F(IR_BuiltinPolyfillSpirvTest, TextureStore_2D) {
     auto format = builtin::TexelFormat::kR32Float;
     auto* t = b.FunctionParam("t", ty.Get<type::StorageTexture>(
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir.cc b/src/tint/writer/spirv/ir/generator_impl_ir.cc
index 99c13c8..31ac619 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir.cc
@@ -1509,6 +1509,12 @@
         case ir::IntrinsicCall::Kind::kSpirvImageFetch:
             op = spv::Op::OpImageFetch;
             break;
+        case ir::IntrinsicCall::Kind::kSpirvImageGather:
+            op = spv::Op::OpImageGather;
+            break;
+        case ir::IntrinsicCall::Kind::kSpirvImageDrefGather:
+            op = spv::Op::OpImageDrefGather;
+            break;
         case ir::IntrinsicCall::Kind::kSpirvImageQuerySize:
             module_.PushCapability(SpvCapabilityImageQuery);
             op = spv::Op::OpImageQuerySize;
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir_texture_builtin_test.cc b/src/tint/writer/spirv/ir/generator_impl_ir_texture_builtin_test.cc
index 13c153c..831e963 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir_texture_builtin_test.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_texture_builtin_test.cc
@@ -156,12 +156,21 @@
         func->SetParams(std::move(func_params));
 
         b.With(func->Block(), [&] {
-            utils::Vector<ir::Value*, 4> args = {t};
+            uint32_t arg_value = 1;
+
+            utils::Vector<ir::Value*, 4> args;
+            if (function == builtin::Function::kTextureGather &&
+                params.texture_type != kDepthTexture) {
+                // Special case for textureGather, which has a component argument first.
+                auto* component = MakeScalarValue(kU32, arg_value++);
+                args.Push(component);
+                mod.SetName(component, "component");
+            }
+            args.Push(t);
             if (s) {
                 args.Push(s);
             }
 
-            uint32_t arg_value = 1;
             for (const auto& arg : params.args) {
                 auto* value = MakeScalarValue(arg.type, arg_value++);
                 if (arg.width > 1) {
@@ -966,6 +975,272 @@
     PrintCase);
 
 ////////////////////////////////////////////////////////////////
+//// textureGather
+////////////////////////////////////////////////////////////////
+using TextureGather = TextureBuiltinTest;
+TEST_P(TextureGather, Emit) {
+    Run(builtin::Function::kTextureGather, kSampler);
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    TextureGather,
+    testing::Values(
+        TextureBuiltinTestCase{
+            kSampledTexture,
+            type::TextureDimension::k2d,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%result = OpImageGather %v4float %10 %coords %component None",
+            },
+        },
+        TextureBuiltinTestCase{
+            kSampledTexture,
+            type::TextureDimension::k2d,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}, {"offset", 2, kI32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%result = OpImageGather %v4float %10 %coords %component ConstOffset %offset",
+            },
+        },
+        TextureBuiltinTestCase{
+            kSampledTexture,
+            type::TextureDimension::k2dArray,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}, {"array_idx", 1, kI32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%12 = OpConvertSToF %float %array_idx",
+                "%16 = OpCompositeConstruct %v3float %coords %12",
+                "%result = OpImageGather %v4float %10 %16 %component None",
+            },
+        },
+        TextureBuiltinTestCase{
+            kSampledTexture,
+            type::TextureDimension::k2dArray,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}, {"array_idx", 1, kI32}, {"offset", 2, kI32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%12 = OpConvertSToF %float %array_idx",
+                "%16 = OpCompositeConstruct %v3float %coords %12",
+                "%result = OpImageGather %v4float %10 %16 %component ConstOffset %offset",
+            },
+        },
+        TextureBuiltinTestCase{
+            kSampledTexture,
+            type::TextureDimension::kCube,
+            /* texel type */ kF32,
+            {{"coords", 3, kF32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%result = OpImageGather %v4float %10 %coords %component None",
+            },
+        },
+        TextureBuiltinTestCase{
+            kSampledTexture,
+            type::TextureDimension::kCubeArray,
+            /* texel type */ kF32,
+            {{"coords", 3, kF32}, {"array_idx", 1, kI32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%12 = OpConvertSToF %float %array_idx",
+                "%15 = OpCompositeConstruct %v4float %coords %12",
+                "%result = OpImageGather %v4float %10 %15 %component None",
+            },
+        },
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::k2d,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%result = OpImageGather %v4float %10 %coords %uint_0 None",
+            },
+        },
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::k2d,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}, {"offset", 2, kI32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%result = OpImageGather %v4float %10 %coords %uint_0 ConstOffset %offset",
+            },
+        },
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::kCube,
+            /* texel type */ kF32,
+            {{"coords", 3, kF32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%result = OpImageGather %v4float %10 %coords %uint_0 None",
+            },
+        },
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::k2dArray,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}, {"array_idx", 1, kI32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%12 = OpConvertSToF %float %array_idx",
+                "%16 = OpCompositeConstruct %v3float %coords %12",
+                "%result = OpImageGather %v4float %10 %16 %uint_0 None",
+            },
+        },
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::k2dArray,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}, {"array_idx", 1, kI32}, {"offset", 2, kI32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%12 = OpConvertSToF %float %array_idx",
+                "%16 = OpCompositeConstruct %v3float %coords %12",
+                "%result = OpImageGather %v4float %10 %16 %uint_0 ConstOffset %offset",
+            },
+        },
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::kCubeArray,
+            /* texel type */ kF32,
+            {{"coords", 3, kF32}, {"array_idx", 1, kI32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%12 = OpConvertSToF %float %array_idx",
+                "%15 = OpCompositeConstruct %v4float %coords %12",
+                "%result = OpImageGather %v4float %10 %15 %uint_0 None",
+            },
+        },
+
+        // Test some textures with integer texel types.
+        TextureBuiltinTestCase{
+            kSampledTexture,
+            type::TextureDimension::k2d,
+            /* texel type */ kI32,
+            {{"coords", 2, kF32}},
+            {"result", 4, kI32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%result = OpImageGather %v4int %10 %coords %component None",
+            },
+        },
+        TextureBuiltinTestCase{
+            kSampledTexture,
+            type::TextureDimension::k2d,
+            /* texel type */ kU32,
+            {{"coords", 2, kF32}},
+            {"result", 4, kU32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%result = OpImageGather %v4uint %10 %coords %component None",
+            },
+        }),
+    PrintCase);
+
+////////////////////////////////////////////////////////////////
+//// textureGatherCompare
+////////////////////////////////////////////////////////////////
+using TextureGatherCompare = TextureBuiltinTest;
+TEST_P(TextureGatherCompare, Emit) {
+    Run(builtin::Function::kTextureGatherCompare, kComparisonSampler);
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    TextureGatherCompare,
+    testing::Values(
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::k2d,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}, {"depth", 1, kF32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%result = OpImageDrefGather %v4float %10 %coords %depth None",
+            },
+        },
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::k2d,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}, {"depth", 1, kF32}, {"offset", 2, kI32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%result = OpImageDrefGather %v4float %10 %coords %depth ConstOffset %offset",
+            },
+        },
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::k2dArray,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}, {"array_idx", 1, kI32}, {"depth", 1, kF32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%12 = OpConvertSToF %float %array_idx",
+                "%16 = OpCompositeConstruct %v3float %coords %12",
+                "%result = OpImageDrefGather %v4float %10 %16 %depth None",
+            },
+        },
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::k2dArray,
+            /* texel type */ kF32,
+            {{"coords", 2, kF32}, {"array_idx", 1, kI32}, {"depth", 1, kF32}, {"offset", 2, kI32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%12 = OpConvertSToF %float %array_idx",
+                "%16 = OpCompositeConstruct %v3float %coords %12",
+                "%result = OpImageDrefGather %v4float %10 %16 %depth ConstOffset %offset",
+            },
+        },
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::kCube,
+            /* texel type */ kF32,
+            {{"coords", 3, kF32}, {"depth", 1, kF32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%result = OpImageDrefGather %v4float %10 %coords %depth None",
+            },
+        },
+        TextureBuiltinTestCase{
+            kDepthTexture,
+            type::TextureDimension::kCubeArray,
+            /* texel type */ kF32,
+            {{"coords", 3, kF32}, {"array_idx", 1, kI32}, {"depth", 1, kF32}},
+            {"result", 4, kF32},
+            {
+                "%10 = OpSampledImage %11 %t %s",
+                "%12 = OpConvertSToF %float %array_idx",
+                "%15 = OpCompositeConstruct %v4float %coords %12",
+                "%result = OpImageDrefGather %v4float %10 %15 %depth None",
+            },
+        }),
+    PrintCase);
+
+////////////////////////////////////////////////////////////////
 //// textureLoad
 ////////////////////////////////////////////////////////////////
 using TextureLoad = TextureBuiltinTest;