[ir][spirv-writer] Handle texture sample builtins

Use the BuiltinPolyfillSpirv transform to replace texture sample
builtins with calls to SPIR-V intrinsic functions that will create the
`OpSampledImage` and then execute an `OpImageSample*` instruction.

A `LiteralOperand` subclass of `ir::Constant` is used to represent the
literal 'image operands' operand, and a `SampledImage` subclass of
`type::Type` is used to represent the `OpSampledImage` type.

Bug: tint:1906
Change-Id: Id3fd166f1cf5772fd75aed5cbeb8c3c02ea65197
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/141230
Reviewed-by: Ben Clayton <bclayton@google.com>
Auto-Submit: James Price <jrprice@google.com>
Commit-Queue: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/writer/spirv/ir/generator_impl_ir.cc b/src/tint/writer/spirv/ir/generator_impl_ir.cc
index 024aa59..479cbd9 100644
--- a/src/tint/writer/spirv/ir/generator_impl_ir.cc
+++ b/src/tint/writer/spirv/ir/generator_impl_ir.cc
@@ -147,6 +147,15 @@
             return s;
         },
 
+        // Dedup a SampledImage if its underlying image will be deduped.
+        [&](const ir::transform::BuiltinPolyfillSpirv::SampledImage* si) -> const type::Type* {
+            auto* img = DedupType(si->Image(), types);
+            if (img != si->Image()) {
+                return types.Get<ir::transform::BuiltinPolyfillSpirv::SampledImage>(img);
+            }
+            return si;
+        },
+
         [&](Default) { return ty; });
 }
 
@@ -236,6 +245,11 @@
 }
 
 uint32_t GeneratorImplIr::Constant(ir::Constant* constant) {
+    // If it is a literal operand, just return the value.
+    if (auto* literal = constant->As<ir::transform::BuiltinPolyfillSpirv::LiteralOperand>()) {
+        return literal->Value()->ValueAs<uint32_t>();
+    }
+
     auto id = Constant(constant->Value());
 
     // Set the name for the SPIR-V result ID if provided in the module.
@@ -375,6 +389,9 @@
             [&](const type::Struct* str) { EmitStructType(id, str, addrspace); },
             [&](const type::Texture* tex) { EmitTextureType(id, tex); },
             [&](const type::Sampler*) { module_.PushType(spv::Op::OpTypeSampler, {id}); },
+            [&](const ir::transform::BuiltinPolyfillSpirv::SampledImage* s) {
+                module_.PushType(spv::Op::OpTypeSampledImage, {id, Type(s->Image())});
+            },
             [&](Default) {
                 TINT_ICE(Writer, diagnostics_) << "unhandled type: " << ty->FriendlyName();
             });
@@ -1403,6 +1420,21 @@
         case ir::IntrinsicCall::Kind::kSpirvSelect:
             op = spv::Op::OpSelect;
             break;
+        case ir::IntrinsicCall::Kind::kSpirvSampledImage:
+            op = spv::Op::OpSampledImage;
+            break;
+        case ir::IntrinsicCall::Kind::kSpirvImageSampleImplicitLod:
+            op = spv::Op::OpImageSampleImplicitLod;
+            break;
+        case ir::IntrinsicCall::Kind::kSpirvImageSampleExplicitLod:
+            op = spv::Op::OpImageSampleExplicitLod;
+            break;
+        case ir::IntrinsicCall::Kind::kSpirvImageSampleDrefImplicitLod:
+            op = spv::Op::OpImageSampleDrefImplicitLod;
+            break;
+        case ir::IntrinsicCall::Kind::kSpirvImageSampleDrefExplicitLod:
+            op = spv::Op::OpImageSampleDrefExplicitLod;
+            break;
     }
 
     OperandList operands = {Type(call->Result()->Type()), id};
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
new file mode 100644
index 0000000..90711e7
--- /dev/null
+++ b/src/tint/writer/spirv/ir/generator_impl_ir_texture_builtin_test.cc
@@ -0,0 +1,597 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/writer/spirv/ir/test_helper_ir.h"
+
+#include "src/tint/builtin/function.h"
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace tint::writer::spirv {
+namespace {
+
+/// An additional argument to a texture builtin.
+struct Arg {
+    /// The argument name.
+    const char* name;
+    /// The vector width of the argument (1 means scalar).
+    uint32_t width;
+    /// The element type of the argument.
+    TestElementType type;
+};
+
+/// A parameterized texture builtin function test case.
+struct TextureBuiltinTestCase {
+    /// The builtin function.
+    enum builtin::Function function;
+    /// The builtin function arguments.
+    utils::Vector<Arg, 4> optional_args;
+    /// The expected SPIR-V instruction string for the texture call.
+    const char* texture_call;
+};
+
+std::string PrintCase(testing::TestParamInfo<TextureBuiltinTestCase> cc) {
+    utils::StringStream ss;
+    ss << cc.param.function;
+    for (const auto& arg : cc.param.optional_args) {
+        ss << "_" << arg.name;
+    }
+    return ss.str();
+}
+
+class TextureBuiltinTest : public SpvGeneratorImplTestWithParam<TextureBuiltinTestCase> {
+  protected:
+    void Run(const type::Texture* texture_ty,
+             const type::Sampler* sampler_ty,
+             const type::Type* coord_ty,
+             const type::Type* return_ty) {
+        auto params = GetParam();
+
+        auto* t = b.FunctionParam("t", texture_ty);
+        auto* s = b.FunctionParam("s", sampler_ty);
+        auto* coord = b.FunctionParam("coords", coord_ty);
+        auto* func = b.Function("foo", return_ty);
+        func->SetParams({t, s, coord});
+
+        b.With(func->Block(), [&] {
+            utils::Vector<ir::Value*, 4> args = {t, s, coord};
+            uint32_t arg_value = 1;
+            for (const auto& arg : params.optional_args) {
+                auto* value = MakeScalarValue(arg.type, arg_value++);
+                if (arg.width > 1) {
+                    value = b.Constant(mod.constant_values.Splat(ty.vec(value->Type(), arg.width),
+                                                                 value->Value(), arg.width));
+                }
+                args.Push(value);
+                mod.SetName(value, arg.name);
+            }
+            auto* result = b.Call(return_ty, params.function, std::move(args));
+            b.Return(func, result);
+            mod.SetName(result, "result");
+        });
+
+        ASSERT_TRUE(Generate()) << Error() << output_;
+        EXPECT_INST(params.texture_call);
+    }
+};
+
+using Texture1D = TextureBuiltinTest;
+TEST_P(Texture1D, Emit) {
+    Run(ty.Get<type::SampledTexture>(type::TextureDimension::k1d, ty.f32()),
+        ty.sampler(),   // sampler type
+        ty.f32(),       // coord type
+        ty.vec4<f32>()  // return type
+    );
+    EXPECT_INST("%11 = OpSampledImage %12 %t %s");
+}
+INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest,
+                         Texture1D,
+                         testing::Values(TextureBuiltinTestCase{
+                             builtin::Function::kTextureSample,
+                             {},
+                             "OpImageSampleImplicitLod %v4float %11 %coords None",
+                         }),
+                         PrintCase);
+
+using Texture2D = TextureBuiltinTest;
+TEST_P(Texture2D, Emit) {
+    Run(ty.Get<type::SampledTexture>(type::TextureDimension::k2d, ty.f32()),
+        ty.sampler(),    // sampler type
+        ty.vec2<f32>(),  // coord type
+        ty.vec4<f32>()   // return type
+    );
+    EXPECT_INST("%12 = OpSampledImage %13 %t %s");
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    Texture2D,
+    testing::Values(
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSample,
+            {},
+            "OpImageSampleImplicitLod %v4float %12 %coords None",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSample,
+            {{"offset", 2, kI32}},
+            "OpImageSampleImplicitLod %v4float %12 %coords ConstOffset %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleBias,
+            {{"bias", 1, kF32}},
+            "OpImageSampleImplicitLod %v4float %12 %coords Bias %bias",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleBias,
+            {{"bias", 1, kF32}, {"offset", 2, kI32}},
+            "OpImageSampleImplicitLod %v4float %12 %coords Bias|ConstOffset %bias %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleGrad,
+            {{"ddx", 2, kF32}, {"ddy", 2, kF32}},
+            "OpImageSampleExplicitLod %v4float %12 %coords Grad %ddx %ddy",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleGrad,
+            {{"ddx", 2, kF32}, {"ddy", 2, kF32}, {"offset", 2, kI32}},
+            "OpImageSampleExplicitLod %v4float %12 %coords Grad|ConstOffset %ddx %ddy %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleLevel,
+            {{"lod", 1, kF32}},
+            "OpImageSampleExplicitLod %v4float %12 %coords Lod %lod",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleLevel,
+            {{"lod", 1, kF32}, {"offset", 2, kI32}},
+            "OpImageSampleExplicitLod %v4float %12 %coords Lod|ConstOffset %lod %offset",
+        }),
+    PrintCase);
+
+using Texture2DArray = TextureBuiltinTest;
+TEST_P(Texture2DArray, Emit) {
+    Run(ty.Get<type::SampledTexture>(type::TextureDimension::k2dArray, ty.f32()),
+        ty.sampler(),    // sampler type
+        ty.vec2<f32>(),  // coord type
+        ty.vec4<f32>()   // return type
+    );
+    EXPECT_INST("%12 = OpSampledImage %13 %t %s");
+    EXPECT_INST("%14 = OpConvertSToF %float %array_idx");
+    EXPECT_INST("%18 = OpCompositeConstruct %v3float %coords %14");
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    Texture2DArray,
+    testing::Values(
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSample,
+            {{"array_idx", 1, kI32}},
+            "OpImageSampleImplicitLod %v4float %12 %18 None",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSample,
+            {{"array_idx", 1, kI32}, {"offset", 2, kI32}},
+            "OpImageSampleImplicitLod %v4float %12 %18 ConstOffset %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleBias,
+            {{"array_idx", 1, kI32}, {"bias", 1, kF32}},
+            "OpImageSampleImplicitLod %v4float %12 %18 Bias %bias",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleBias,
+            {{"array_idx", 1, kI32}, {"bias", 1, kF32}, {"offset", 2, kI32}},
+            "OpImageSampleImplicitLod %v4float %12 %18 Bias|ConstOffset %bias %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleGrad,
+            {{"array_idx", 1, kI32}, {"ddx", 2, kF32}, {"ddy", 2, kF32}},
+            "OpImageSampleExplicitLod %v4float %12 %18 Grad %ddx %ddy",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleGrad,
+            {{"array_idx", 1, kI32}, {"ddx", 2, kF32}, {"ddy", 2, kF32}, {"offset", 2, kI32}},
+            "OpImageSampleExplicitLod %v4float %12 %18 Grad|ConstOffset %ddx %ddy %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleLevel,
+            {{"array_idx", 1, kI32}, {"lod", 1, kF32}},
+            "OpImageSampleExplicitLod %v4float %12 %18 Lod %lod",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleLevel,
+            {{"array_idx", 1, kI32}, {"lod", 1, kF32}, {"offset", 2, kI32}},
+            "OpImageSampleExplicitLod %v4float %12 %18 Lod|ConstOffset %lod %offset",
+        }),
+    PrintCase);
+
+using Texture3D = TextureBuiltinTest;
+TEST_P(Texture3D, Emit) {
+    Run(ty.Get<type::SampledTexture>(type::TextureDimension::k3d, ty.f32()),
+        ty.sampler(),    // sampler type
+        ty.vec3<f32>(),  // coord type
+        ty.vec4<f32>()   // return type
+    );
+    EXPECT_INST("%12 = OpSampledImage %13 %t %s");
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    Texture3D,
+    testing::Values(
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSample,
+            {},
+            "OpImageSampleImplicitLod %v4float %12 %coords None",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSample,
+            {{"offset", 3, kI32}},
+            "OpImageSampleImplicitLod %v4float %12 %coords ConstOffset %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleBias,
+            {{"bias", 1, kF32}},
+            "OpImageSampleImplicitLod %v4float %12 %coords Bias %bias",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleBias,
+            {{"bias", 1, kF32}, {"offset", 3, kI32}},
+            "OpImageSampleImplicitLod %v4float %12 %coords Bias|ConstOffset %bias %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleGrad,
+            {{"ddx", 3, kF32}, {"ddy", 3, kF32}},
+            "OpImageSampleExplicitLod %v4float %12 %coords Grad %ddx %ddy",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleGrad,
+            {{"ddx", 3, kF32}, {"ddy", 3, kF32}, {"offset", 3, kI32}},
+            "OpImageSampleExplicitLod %v4float %12 %coords Grad|ConstOffset %ddx %ddy %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleLevel,
+            {{"lod", 1, kF32}},
+            "OpImageSampleExplicitLod %v4float %12 %coords Lod %lod",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleLevel,
+            {{"lod", 1, kF32}, {"offset", 3, kI32}},
+            "OpImageSampleExplicitLod %v4float %12 %coords Lod|ConstOffset %lod %offset",
+        }),
+    PrintCase);
+
+using TextureCube = TextureBuiltinTest;
+TEST_P(TextureCube, Emit) {
+    Run(ty.Get<type::SampledTexture>(type::TextureDimension::kCube, ty.f32()),
+        ty.sampler(),    // sampler type
+        ty.vec3<f32>(),  // coord type
+        ty.vec4<f32>()   // return type
+    );
+    EXPECT_INST("%12 = OpSampledImage %13 %t %s");
+}
+INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest,
+                         TextureCube,
+                         testing::Values(
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSample,
+                                 {},
+                                 "OpImageSampleImplicitLod %v4float %12 %coords None",
+                             },
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSampleBias,
+                                 {{"bias", 1, kF32}},
+                                 "OpImageSampleImplicitLod %v4float %12 %coords Bias %bias",
+                             },
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSampleGrad,
+                                 {{"ddx", 3, kF32}, {"ddy", 3, kF32}},
+                                 "OpImageSampleExplicitLod %v4float %12 %coords Grad %ddx %ddy",
+                             },
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSampleLevel,
+                                 {{"lod", 1, kF32}},
+                                 "OpImageSampleExplicitLod %v4float %12 %coords Lod %lod",
+                             }),
+                         PrintCase);
+
+using TextureCubeArray = TextureBuiltinTest;
+TEST_P(TextureCubeArray, Emit) {
+    Run(ty.Get<type::SampledTexture>(type::TextureDimension::kCubeArray, ty.f32()),
+        ty.sampler(),    // sampler type
+        ty.vec3<f32>(),  // coord type
+        ty.vec4<f32>()   // return type
+    );
+    EXPECT_INST("%12 = OpSampledImage %13 %t %s");
+    EXPECT_INST("%14 = OpConvertSToF %float %array_idx");
+    EXPECT_INST("%17 = OpCompositeConstruct %v4float %coords %14");
+}
+INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest,
+                         TextureCubeArray,
+                         testing::Values(
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSample,
+                                 {{"array_idx", 1, kI32}},
+                                 "OpImageSampleImplicitLod %v4float %12 %17 None",
+                             },
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSampleBias,
+                                 {{"array_idx", 1, kI32}, {"bias", 1, kF32}},
+                                 "OpImageSampleImplicitLod %v4float %12 %17 Bias %bias",
+                             },
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSampleGrad,
+                                 {{"array_idx", 1, kI32}, {"ddx", 3, kF32}, {"ddy", 3, kF32}},
+                                 "OpImageSampleExplicitLod %v4float %12 %17 Grad %ddx %ddy",
+                             },
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSampleLevel,
+                                 {{"array_idx", 1, kI32}, {"lod", 1, kF32}},
+                                 "OpImageSampleExplicitLod %v4float %12 %17 Lod %lod",
+                             }),
+                         PrintCase);
+
+using TextureDepth2D = TextureBuiltinTest;
+TEST_P(TextureDepth2D, Emit) {
+    Run(ty.Get<type::DepthTexture>(type::TextureDimension::k2d),
+        ty.sampler(),    // sampler type
+        ty.vec2<f32>(),  // coord type
+        ty.f32()         // return type
+    );
+    EXPECT_INST("%11 = OpSampledImage %12 %t %s");
+    EXPECT_INST("%result = OpCompositeExtract %float");
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    TextureDepth2D,
+    testing::Values(
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSample,
+            {},
+            "OpImageSampleImplicitLod %v4float %11 %coords None",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSample,
+            {{"offset", 2, kI32}},
+            "OpImageSampleImplicitLod %v4float %11 %coords ConstOffset %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleLevel,
+            {{"lod", 1, kI32}},
+            "OpImageSampleExplicitLod %v4float %11 %coords Lod %13",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleLevel,
+            {{"lod", 1, kI32}, {"offset", 2, kI32}},
+            "OpImageSampleExplicitLod %v4float %11 %coords Lod|ConstOffset %13 %offset",
+        }),
+    PrintCase);
+
+using TextureDepth2D_DepthComparison = TextureBuiltinTest;
+TEST_P(TextureDepth2D_DepthComparison, Emit) {
+    Run(ty.Get<type::DepthTexture>(type::TextureDimension::k2d),
+        ty.comparison_sampler(),  // sampler type
+        ty.vec2<f32>(),           // coord type
+        ty.f32()                  // return type
+    );
+    EXPECT_INST("%11 = OpSampledImage %12 %t %s");
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    TextureDepth2D_DepthComparison,
+    testing::Values(
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompare,
+            {{"depth", 1, kF32}},
+            "OpImageSampleDrefImplicitLod %float %11 %coords %depth",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompare,
+            {{"depth", 1, kF32}, {"offset", 2, kI32}},
+            "OpImageSampleDrefImplicitLod %float %11 %coords %depth ConstOffset %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompareLevel,
+            {{"depth_l0", 1, kF32}},
+            "OpImageSampleDrefExplicitLod %float %11 %coords %depth_l0 Lod %float_0",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompareLevel,
+            {{"depth_l0", 1, kF32}, {"offset", 2, kI32}},
+            "OpImageSampleDrefExplicitLod %float %11 %coords %depth_l0 Lod|ConstOffset %float_0 "
+            "%offset",
+        }),
+    PrintCase);
+
+using TextureDepth2DArray = TextureBuiltinTest;
+TEST_P(TextureDepth2DArray, Emit) {
+    Run(ty.Get<type::DepthTexture>(type::TextureDimension::k2dArray),
+        ty.sampler(),    // sampler type
+        ty.vec2<f32>(),  // coord type
+        ty.f32()         // return type
+    );
+    EXPECT_INST("%11 = OpSampledImage %12 %t %s");
+    EXPECT_INST("%13 = OpConvertSToF %float %array_idx");
+    EXPECT_INST("%17 = OpCompositeConstruct %v3float %coords %13");
+    EXPECT_INST("%result = OpCompositeExtract %float");
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    TextureDepth2DArray,
+    testing::Values(
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSample,
+            {{"array_idx", 1, kI32}},
+            "OpImageSampleImplicitLod %v4float %11 %17 None",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSample,
+            {{"array_idx", 1, kI32}, {"offset", 2, kI32}},
+            "OpImageSampleImplicitLod %v4float %11 %17 ConstOffset %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleLevel,
+            {{"array_idx", 1, kI32}, {"lod", 1, kI32}},
+            "OpImageSampleExplicitLod %v4float %11 %17 Lod %18",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleLevel,
+            {{"array_idx", 1, kI32}, {"lod", 1, kI32}, {"offset", 2, kI32}},
+            "OpImageSampleExplicitLod %v4float %11 %17 Lod|ConstOffset %18 %offset",
+        }),
+    PrintCase);
+
+using TextureDepth2DArray_DepthComparison = TextureBuiltinTest;
+TEST_P(TextureDepth2DArray_DepthComparison, Emit) {
+    Run(ty.Get<type::DepthTexture>(type::TextureDimension::k2dArray),
+        ty.comparison_sampler(),  // sampler type
+        ty.vec2<f32>(),           // coord type
+        ty.f32()                  // return type
+    );
+    EXPECT_INST("%11 = OpSampledImage %12 %t %s");
+    EXPECT_INST("%13 = OpConvertSToF %float %array_idx");
+    EXPECT_INST("%17 = OpCompositeConstruct %v3float %coords %13");
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    TextureDepth2DArray_DepthComparison,
+    testing::Values(
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompare,
+            {{"array_idx", 1, kI32}, {"depth", 1, kF32}},
+            "OpImageSampleDrefImplicitLod %float %11 %17 %depth",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompare,
+            {{"array_idx", 1, kI32}, {"depth", 1, kF32}, {"offset", 2, kI32}},
+            "OpImageSampleDrefImplicitLod %float %11 %17 %depth ConstOffset %offset",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompareLevel,
+            {{"array_idx", 1, kI32}, {"depth_l0", 1, kF32}},
+            "OpImageSampleDrefExplicitLod %float %11 %17 %depth_l0 Lod %float_0",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompareLevel,
+            {{"array_idx", 1, kI32}, {"depth_l0", 1, kF32}, {"offset", 2, kI32}},
+            "OpImageSampleDrefExplicitLod %float %11 %17 %depth_l0 Lod|ConstOffset %float_0 "
+            "%offset",
+        }),
+    PrintCase);
+
+using TextureDepthCube = TextureBuiltinTest;
+TEST_P(TextureDepthCube, Emit) {
+    Run(ty.Get<type::DepthTexture>(type::TextureDimension::kCube),
+        ty.sampler(),    // sampler type
+        ty.vec3<f32>(),  // coord type
+        ty.f32()         // return type
+    );
+    EXPECT_INST("%11 = OpSampledImage %12 %t %s");
+    EXPECT_INST("%result = OpCompositeExtract %float");
+}
+INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest,
+                         TextureDepthCube,
+                         testing::Values(
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSample,
+                                 {},
+                                 "OpImageSampleImplicitLod %v4float %11 %coords None",
+                             },
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSampleLevel,
+                                 {{"lod", 1, kI32}},
+                                 "OpImageSampleExplicitLod %v4float %11 %coords Lod %13",
+                             }),
+                         PrintCase);
+
+using TextureDepthCube_DepthComparison = TextureBuiltinTest;
+TEST_P(TextureDepthCube_DepthComparison, Emit) {
+    Run(ty.Get<type::DepthTexture>(type::TextureDimension::kCube),
+        ty.comparison_sampler(),  // sampler typea
+        ty.vec3<f32>(),           // coord type
+        ty.f32()                  // return type
+    );
+    EXPECT_INST("%11 = OpSampledImage %12 %t %s");
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    TextureDepthCube_DepthComparison,
+    testing::Values(
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompare,
+            {{"depth", 1, kF32}},
+            "OpImageSampleDrefImplicitLod %float %11 %coords %depth",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompareLevel,
+            {{"depth_l0", 1, kF32}},
+            "OpImageSampleDrefExplicitLod %float %11 %coords %depth_l0 Lod %float_0",
+        }),
+    PrintCase);
+
+using TextureDepthCubeArray = TextureBuiltinTest;
+TEST_P(TextureDepthCubeArray, Emit) {
+    Run(ty.Get<type::DepthTexture>(type::TextureDimension::kCubeArray),
+        ty.sampler(),    // sampler type
+        ty.vec3<f32>(),  // coord type
+        ty.f32()         // return type
+    );
+    EXPECT_INST("%11 = OpSampledImage %12 %t %s");
+    EXPECT_INST("%13 = OpConvertSToF %float %array_idx");
+    EXPECT_INST("%17 = OpCompositeConstruct %v4float %coords %13");
+    EXPECT_INST("%result = OpCompositeExtract %float");
+}
+INSTANTIATE_TEST_SUITE_P(SpvGeneratorImplTest,
+                         TextureDepthCubeArray,
+                         testing::Values(
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSample,
+                                 {{"array_idx", 1, kI32}},
+                                 "OpImageSampleImplicitLod %v4float %11 %17 None",
+                             },
+                             TextureBuiltinTestCase{
+                                 builtin::Function::kTextureSampleLevel,
+                                 {{"array_idx", 1, kI32}, {"lod", 1, kI32}},
+                                 "OpImageSampleExplicitLod %v4float %11 %17 Lod %18",
+                             }),
+                         PrintCase);
+
+using TextureDepthCubeArray_DepthComparison = TextureBuiltinTest;
+TEST_P(TextureDepthCubeArray_DepthComparison, Emit) {
+    Run(ty.Get<type::DepthTexture>(type::TextureDimension::kCubeArray),
+        ty.comparison_sampler(),  // sampler type
+        ty.vec3<f32>(),           // coord type
+        ty.f32()                  // return type
+    );
+    EXPECT_INST("%11 = OpSampledImage %12 %t %s");
+    EXPECT_INST("%13 = OpConvertSToF %float %array_idx");
+    EXPECT_INST("%17 = OpCompositeConstruct %v4float %coords %13");
+}
+INSTANTIATE_TEST_SUITE_P(
+    SpvGeneratorImplTest,
+    TextureDepthCubeArray_DepthComparison,
+    testing::Values(
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompare,
+            {{"array_idx", 1, kI32}, {"depth", 1, kF32}},
+            "OpImageSampleDrefImplicitLod %float %11 %17 %depth",
+        },
+        TextureBuiltinTestCase{
+            builtin::Function::kTextureSampleCompareLevel,
+            {{"array_idx", 1, kI32}, {"depth_l0", 1, kF32}},
+            "OpImageSampleDrefExplicitLod %float %11 %17 %depth_l0 Lod %float_0",
+        }),
+    PrintCase);
+
+}  // namespace
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/ir/test_helper_ir.h b/src/tint/writer/spirv/ir/test_helper_ir.h
index 3d86819..54af5ad 100644
--- a/src/tint/writer/spirv/ir/test_helper_ir.h
+++ b/src/tint/writer/spirv/ir/test_helper_ir.h
@@ -176,19 +176,20 @@
 
     /// Helper to make a scalar value with the scalar type `type`.
     /// @param type the element type
+    /// @param value the optional value to use
     /// @returns the scalar value
-    ir::Value* MakeScalarValue(TestElementType type) {
+    ir::Constant* MakeScalarValue(TestElementType type, uint32_t value = 1) {
         switch (type) {
             case kBool:
                 return b.Constant(true);
             case kI32:
-                return b.Constant(i32(1));
+                return b.Constant(i32(value));
             case kU32:
-                return b.Constant(u32(1));
+                return b.Constant(u32(value));
             case kF32:
-                return b.Constant(f32(1));
+                return b.Constant(f32(value));
             case kF16:
-                return b.Constant(f16(1));
+                return b.Constant(f16(value));
         }
         return nullptr;
     }
@@ -196,7 +197,7 @@
     /// Helper to make a vector value with an element type of `type`.
     /// @param type the element type
     /// @returns the vector value
-    ir::Value* MakeVectorValue(TestElementType type) {
+    ir::Constant* MakeVectorValue(TestElementType type) {
         switch (type) {
             case kBool:
                 return b.Constant(mod.constant_values.Composite(