[spirv-writer] Generating texture intrinsic operations

Bug: tint:143
Change-Id: Ibc5b232b952b8ecd8d5d87a7486cdfbc1af29fa4
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/27641
Commit-Queue: David Neto <dneto@google.com>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index fc01258..065bca1 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -1467,6 +1467,10 @@
     params.push_back(Operand::Int(val_id));
   }
 
+  if (ast::intrinsic::IsTextureOperationIntrinsic(name)) {
+    return GenerateTextureIntrinsic(name, call, result_id, params);
+  }
+
   if (ast::intrinsic::IsFineDerivative(name) ||
       ast::intrinsic::IsCoarseDerivative(name)) {
     push_capability(SpvCapabilityDerivativeControl);
@@ -1515,6 +1519,103 @@
   return result_id;
 }
 
+uint32_t Builder::GenerateTextureIntrinsic(const std::string& name,
+                                           ast::CallExpression* call,
+                                           uint32_t result_id,
+                                           OperandList wgsl_params) {
+  auto* texture_type = call->params()[0]
+                           .get()
+                           ->result_type()
+                           ->UnwrapAliasPtrAlias()
+                           ->AsTexture();
+
+  if (name == "texture_load") {
+    auto spirv_params = {std::move(wgsl_params[0]),
+                         std::move(wgsl_params[1]),
+                         std::move(wgsl_params[2]),
+                         std::move(wgsl_params[3]),
+                         Operand::Int(SpvImageOperandsLodMask),
+                         std::move(wgsl_params[4])};
+    auto op = spv::Op::OpImageFetch;
+    if (texture_type->IsStorage()) {
+      op = spv::Op::OpImageRead;
+    }
+    push_function_inst(op, spirv_params);
+    return result_id;
+  }
+
+  spv::Op op = spv::Op::OpNop;
+  OperandList spirv_params = {
+      wgsl_params[0], std::move(wgsl_params[1]),
+      Operand::Int(GenerateSampledImage(texture_type, std::move(wgsl_params[2]),
+                                        std::move(wgsl_params[3]))),
+      std::move(wgsl_params[4])};
+
+  if (name == "texture_sample") {
+    op = spv::Op::OpImageSampleImplicitLod;
+  } else if (name == "texture_sample_level") {
+    op = spv::Op::OpImageSampleExplicitLod;
+    spirv_params.push_back(Operand::Int(SpvImageOperandsLodMask));
+    spirv_params.push_back(std::move(wgsl_params[5]));
+  } else if (name == "texture_sample_bias") {
+    op = spv::Op::OpImageSampleImplicitLod;
+    spirv_params.push_back(Operand::Int(SpvImageOperandsBiasMask));
+    spirv_params.push_back(std::move(wgsl_params[5]));
+  } else if (name == "texture_sample_compare") {
+    op = spv::Op::OpImageSampleDrefExplicitLod;
+    spirv_params.push_back(std::move(wgsl_params[5]));
+
+    spirv_params.push_back(Operand::Int(SpvImageOperandsLodMask));
+    spirv_params.push_back(Operand::Int(
+        GenerateConstantFloatZeroIfNeeded(std::move(wgsl_params[0]))));
+  }
+  if (op == spv::Op::OpNop) {
+    error_ = "unable to determine operator for: " + name;
+    return 0;
+  }
+  push_function_inst(op, spirv_params);
+
+  return result_id;
+}
+
+uint32_t Builder::GenerateSampledImage(ast::type::Type* texture_type,
+                                       Operand texture_operand,
+                                       Operand sampler_operand) {
+  uint32_t sampled_image_type_id = 0;
+  auto val = texture_type_name_to_sampled_image_type_id_.find(
+      texture_type->type_name());
+  if (val != texture_type_name_to_sampled_image_type_id_.end()) {
+    // The sampled image type is already created.
+    sampled_image_type_id = val->second;
+  } else {
+    // We need to create the sampled image type and cache the result.
+    auto sampled_image_type = result_op();
+    sampled_image_type_id = sampled_image_type.to_i();
+    auto texture_type_id = GenerateTypeIfNeeded(texture_type);
+    push_type(spv::Op::OpTypeSampledImage,
+              {sampled_image_type, Operand::Int(texture_type_id)});
+    texture_type_name_to_sampled_image_type_id_[texture_type->type_name()] =
+        sampled_image_type_id;
+  }
+
+  auto sampled_image = result_op();
+  push_function_inst(spv::Op::OpSampledImage,
+                     {Operand::Int(sampled_image_type_id), sampled_image,
+                      texture_operand, sampler_operand});
+
+  return sampled_image.to_i();
+}
+
+uint32_t Builder::GenerateConstantFloatZeroIfNeeded(Operand float_operand) {
+  if (constant_float_zero_id_ == 0) {
+    auto float_0 = result_op();
+    push_type(spv::Op::OpConstant,
+              {std::move(float_operand), float_0, Operand::Int(0)});
+    constant_float_zero_id_ = float_0.to_i();
+  }
+  return constant_float_zero_id_;
+}
+
 uint32_t Builder::GenerateAsExpression(ast::AsExpression* as) {
   auto result = result_op();
   auto result_id = result.to_i();
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
index a1d5f56..1f828f8 100644
--- a/src/writer/spirv/builder.h
+++ b/src/writer/spirv/builder.h
@@ -286,6 +286,26 @@
   /// @returns the expression ID on success or 0 otherwise
   uint32_t GenerateIntrinsic(const std::string& name,
                              ast::CallExpression* call);
+  /// Generates a texture intrinsic call
+  /// @param name the texture intrinsic name
+  /// @param call the call expression
+  /// @returns the expression ID on success or 0 otherwise
+  uint32_t GenerateTextureIntrinsic(const std::string& name,
+                                    ast::CallExpression* call,
+                                    uint32_t result_id,
+                                    OperandList params);
+  /// Generates a sampled image
+  /// @param texture_type the texture type
+  /// @param texture_operand the texture operand
+  /// @param sampler_operand the sampler operand
+  /// @returns the expression ID
+  uint32_t GenerateSampledImage(ast::type::Type* texture_type,
+                                Operand texture_operand,
+                                Operand sampler_operand);
+  /// Generates a constant float zero.
+  /// @param float_operand the f32 type operand
+  /// @returns the expression ID
+  uint32_t GenerateConstantFloatZeroIfNeeded(Operand float_operand);
   /// Generates a cast expression
   /// @param expr the expression to generate
   /// @returns the expression ID on success or 0 otherwise
@@ -424,10 +444,13 @@
   std::unordered_map<std::string, ast::Function*> func_name_to_func_;
   std::unordered_map<std::string, uint32_t> type_name_to_id_;
   std::unordered_map<std::string, uint32_t> const_to_id_;
+  std::unordered_map<std::string, uint32_t>
+      texture_type_name_to_sampled_image_type_id_;
   ScopeStack<uint32_t> scope_stack_;
   std::unordered_map<uint32_t, ast::Variable*> spirv_id_to_variable_;
   std::vector<uint32_t> merge_stack_;
   std::vector<uint32_t> continue_stack_;
+  uint32_t constant_float_zero_id_ = 0;
 };
 
 }  // namespace spirv
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index 3bb0b31..8027b81 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -18,8 +18,13 @@
 #include "src/ast/call_expression.h"
 #include "src/ast/identifier_expression.h"
 #include "src/ast/type/bool_type.h"
+#include "src/ast/type/depth_texture_type.h"
 #include "src/ast/type/f32_type.h"
+#include "src/ast/type/i32_type.h"
 #include "src/ast/type/matrix_type.h"
+#include "src/ast/type/sampled_texture_type.h"
+#include "src/ast/type/sampler_type.h"
+#include "src/ast/type/u32_type.h"
 #include "src/ast/type/vector_type.h"
 #include "src/ast/variable.h"
 #include "src/context.h"
@@ -408,6 +413,869 @@
 )");
 }
 
+enum class TextureType { kF32, kI32, kU32 };
+inline std::ostream& operator<<(std::ostream& out, TextureType data) {
+  if (data == TextureType::kF32) {
+    out << "f32";
+  } else if (data == TextureType::kI32) {
+    out << "i32";
+  } else {
+    out << "u32";
+  }
+  return out;
+}
+
+struct TextureTestParams {
+  ast::type::TextureDimension dim;
+  TextureType type = TextureType::kF32;
+  ast::type::ImageFormat format = ast::type::ImageFormat::kR16Float;
+};
+inline std::ostream& operator<<(std::ostream& out, TextureTestParams data) {
+  out << data.dim << "_" << data.type;
+  return out;
+}
+
+class Builder_TextureOperation
+    : public testing::TestWithParam<TextureTestParams> {
+ public:
+  Builder_TextureOperation()
+      : td_(std::make_unique<TypeDeterminer>(&ctx_, &mod_)),
+        b_(std::make_unique<Builder>(&mod_)) {}
+
+  TypeDeterminer* td() const { return td_.get(); }
+  Context* ctx() { return &ctx_; }
+  Builder* b() const { return b_.get(); }
+
+  std::unique_ptr<ast::type::Type> get_coords_type(
+      ast::type::TextureDimension dim,
+      ast::type::Type* type) {
+    if (dim == ast::type::TextureDimension::k1d) {
+      if (type->IsI32()) {
+        return std::make_unique<ast::type::I32Type>();
+      } else if (type->IsU32()) {
+        return std::make_unique<ast::type::U32Type>();
+      } else {
+        return std::make_unique<ast::type::F32Type>();
+      }
+    } else if (dim == ast::type::TextureDimension::k1dArray ||
+               dim == ast::type::TextureDimension::k2d ||
+               dim == ast::type::TextureDimension::k2dMs) {
+      return std::make_unique<ast::type::VectorType>(type, 2);
+    } else if (dim == ast::type::TextureDimension::kCubeArray) {
+      return std::make_unique<ast::type::VectorType>(type, 4);
+    } else {
+      return std::make_unique<ast::type::VectorType>(type, 3);
+    }
+  }
+
+  void add_call_param(std::string name,
+                      ast::type::Type* type,
+                      ast::ExpressionList* call_params) {
+    auto var =
+        std::make_unique<ast::Variable>(name, ast::StorageClass::kNone, type);
+    td()->RegisterVariableForTesting(var.get());
+
+    call_params->push_back(std::make_unique<ast::IdentifierExpression>(name));
+    ASSERT_TRUE(b()->GenerateGlobalVariable(var.release())) << b()->error();
+  }
+
+  std::unique_ptr<ast::type::Type> subtype(TextureType type) {
+    if (type == TextureType::kF32) {
+      return std::make_unique<ast::type::F32Type>();
+    }
+    if (type == TextureType::kI32) {
+      return std::make_unique<ast::type::I32Type>();
+    }
+    return std::make_unique<ast::type::U32Type>();
+  }
+
+  std::string texture_line(
+      ast::type::TextureDimension dim,
+      bool unknown_format,
+      TextureType type,
+      uint32_t depth_literal,
+      uint32_t sampled_literal,
+      ast::type::ImageFormat format = ast::type::ImageFormat::kR8Unorm) {
+    std::string res = "%6 = OpTypeImage ";
+
+    if (type == TextureType::kF32) {
+      res += "%1 ";
+    } else if (type == TextureType::kU32) {
+      res += "%2 ";
+    } else {
+      res += "%3 ";
+    }
+
+    if (dim == ast::type::TextureDimension::k1d ||
+        dim == ast::type::TextureDimension::k1dArray) {
+      res += "1D ";
+    } else if (dim == ast::type::TextureDimension::k3d) {
+      res += "3D ";
+    } else if (dim == ast::type::TextureDimension::kCube ||
+               dim == ast::type::TextureDimension::kCubeArray) {
+      res += "Cube ";
+    } else {
+      res += "2D ";
+    }
+
+    res += std::to_string(depth_literal) + " ";
+
+    if (dim == ast::type::TextureDimension::k1dArray ||
+        dim == ast::type::TextureDimension::k2dArray ||
+        dim == ast::type::TextureDimension::k2dMsArray ||
+        dim == ast::type::TextureDimension::kCubeArray) {
+      res += "1 ";
+    } else {
+      res += "0 ";
+    }
+
+    if (dim == ast::type::TextureDimension::k2dMs ||
+        dim == ast::type::TextureDimension::k2dMsArray) {
+      res += "1 ";
+    } else {
+      res += "0 ";
+    }
+
+    res += std::to_string(sampled_literal) + " ";
+
+    if (unknown_format) {
+      res += "Unknown\n";
+    } else if (format == ast::type::ImageFormat::kR16Float) {
+      res += "R16f\n";
+    } else if (format == ast::type::ImageFormat::kR16Sint) {
+      res += "R16i\n";
+    } else {
+      res += "R8\n";
+    }
+
+    return res;
+  }
+
+ private:
+  Context ctx_;
+  ast::Module mod_;
+  std::unique_ptr<TypeDeterminer> td_;
+  std::unique_ptr<Builder> b_;
+};
+
+class Builder_TextureLoad : public Builder_TextureOperation {
+ public:
+  std::string generate_type_str(ast::type::TextureDimension dim,
+                                ast::type::ImageFormat format,
+                                bool unknown_format,
+                                TextureType type,
+                                uint32_t depth_literal,
+                                uint32_t sampled_literal,
+                                uint32_t type_id,
+                                uint32_t coords_length) {
+    std::string type_str = R"(%1 = OpTypeFloat 32
+%2 = OpTypeInt 32 0
+%3 = OpTypeInt 32 1
+)";
+
+    type_str += texture_line(dim, unknown_format, type, depth_literal,
+                             sampled_literal, format);
+
+    type_str += R"(%5 = OpTypePointer Private %6
+%7 = OpConstantNull %6
+%4 = OpVariable %5 Private %7
+)";
+
+    if (coords_length > 1) {
+      type_str += "%10 = OpTypeVector %3 " + std::to_string(coords_length) +
+                  "\n" +
+                  R"(%9 = OpTypePointer Private %10
+%11 = OpConstantNull %10
+%8 = OpVariable %9 Private %11
+%13 = OpTypePointer Private %3
+%14 = OpConstantNull %3
+%12 = OpVariable %13 Private %14
+%16 = OpTypeVector %)" +
+                  std::to_string(type_id) + " 4\n";
+    } else {
+      type_str += R"(%9 = OpTypePointer Private %3
+%10 = OpConstantNull %3
+%8 = OpVariable %9 Private %10
+%11 = OpVariable %9 Private %10
+%13 = OpTypeVector %)" +
+                  std::to_string(type_id) + " 4\n";
+    }
+
+    return type_str;
+  }
+
+  std::string generate_ops_str(uint32_t coords_length, std::string op_name) {
+    if (coords_length == 1) {
+      return R"(%14 = OpLoad %6 %4
+%15 = OpLoad %3 %8
+%16 = OpLoad %3 %11
+%12 = )" + op_name +
+             R"( %13 %14 %15 Lod %16
+)";
+    }
+
+    return R"(%17 = OpLoad %6 %4
+%18 = OpLoad %10 %8
+%19 = OpLoad %3 %12
+%15 = )" + op_name +
+           R"( %16 %17 %18 Lod %19
+)";
+  }
+};
+
+TEST_P(Builder_TextureLoad, StorageReadonly) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+  auto format = GetParam().format;
+
+  ast::type::F32Type f32;
+  b()->GenerateTypeIfNeeded(&f32);
+  ast::type::U32Type u32;
+  b()->GenerateTypeIfNeeded(&u32);
+  ast::type::I32Type i32;
+  b()->GenerateTypeIfNeeded(&i32);
+
+  uint32_t type_id = 1;
+  if (type == TextureType::kU32) {
+    type_id = 2;
+  } else if (type == TextureType::kI32) {
+    type_id = 3;
+  }
+
+  auto coords_type = get_coords_type(dim, &i32);
+
+  uint32_t coords_length = 1;
+  if (coords_type->IsVector()) {
+    coords_length = coords_type->AsVector()->size();
+  }
+
+  auto* texture_type =
+      ctx()->type_mgr().Get(std::make_unique<ast::type::StorageTextureType>(
+          dim, ast::type::StorageAccess::kRead, format));
+
+  EXPECT_TRUE(td()->Determine());
+
+  ast::ExpressionList call_params;
+  b()->push_function(Function{});
+
+  add_call_param("texture", texture_type, &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+  add_call_param("lod", &i32, &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_load"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  if (coords_length > 1) {
+    EXPECT_EQ(b()->GenerateExpression(&expr), 15u) << b()->error();
+  } else {
+    EXPECT_EQ(b()->GenerateExpression(&expr), 12u) << b()->error();
+  }
+
+  EXPECT_EQ(DumpInstructions(b()->types()),
+            generate_type_str(dim, format, false, type, 0, 2, type_id,
+                              coords_length));
+
+  EXPECT_EQ(DumpInstructions(b()->functions()[0].instructions()),
+            generate_ops_str(coords_length, "OpImageRead"));
+}
+
+TEST_P(Builder_TextureLoad, Sampled) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+  auto format = GetParam().format;
+
+  ast::type::F32Type f32;
+  b()->GenerateTypeIfNeeded(&f32);
+  ast::type::U32Type u32;
+  b()->GenerateTypeIfNeeded(&u32);
+  ast::type::I32Type i32;
+  b()->GenerateTypeIfNeeded(&i32);
+
+  uint32_t type_id = 1;
+  if (type == TextureType::kU32) {
+    type_id = 2;
+  } else if (type == TextureType::kI32) {
+    type_id = 3;
+  }
+
+  auto coords_type = get_coords_type(dim, &i32);
+
+  uint32_t coords_length = 1;
+  if (coords_type->IsVector()) {
+    coords_length = coords_type->AsVector()->size();
+  }
+
+  std::unique_ptr<ast::type::Type> s = subtype(type);
+  auto* texture_type = ctx()->type_mgr().Get(
+      std::make_unique<ast::type::SampledTextureType>(dim, s.get()));
+
+  EXPECT_TRUE(td()->Determine());
+
+  ast::ExpressionList call_params;
+  b()->push_function(Function{});
+
+  add_call_param("texture", texture_type, &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+  add_call_param("lod", &i32, &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_load"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  if (coords_length > 1) {
+    EXPECT_EQ(b()->GenerateExpression(&expr), 15u) << b()->error();
+  } else {
+    EXPECT_EQ(b()->GenerateExpression(&expr), 12u) << b()->error();
+  }
+
+  EXPECT_EQ(
+      DumpInstructions(b()->types()),
+      generate_type_str(dim, format, true, type, 0, 1, type_id, coords_length));
+
+  EXPECT_EQ(DumpInstructions(b()->functions()[0].instructions()),
+            generate_ops_str(coords_length, "OpImageFetch"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    Builder_TextureLoad,
+    testing::Values(
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kF32,
+                          ast::type::ImageFormat::kR16Float},
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kI32,
+                          ast::type::ImageFormat::kR16Sint},
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kU32,
+                          ast::type::ImageFormat::kR8Unorm},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kF32, ast::type::ImageFormat::kR16Float},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kI32, ast::type::ImageFormat::kR16Sint},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kU32, ast::type::ImageFormat::kR8Unorm},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kF32,
+                          ast::type::ImageFormat::kR16Float},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kI32,
+                          ast::type::ImageFormat::kR16Sint},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kU32,
+                          ast::type::ImageFormat::kR8Unorm},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kF32, ast::type::ImageFormat::kR16Float},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kI32, ast::type::ImageFormat::kR16Sint},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kU32, ast::type::ImageFormat::kR8Unorm},
+        TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kF32,
+                          ast::type::ImageFormat::kR16Float},
+        TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kI32,
+                          ast::type::ImageFormat::kR16Sint},
+        TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kU32,
+                          ast::type::ImageFormat::kR8Unorm}));
+
+class Builder_SampledTextureOperation : public Builder_TextureOperation {
+ public:
+  std::string generate_type_str(ast::type::TextureDimension dim,
+                                bool unknown_format,
+                                TextureType type,
+                                uint32_t depth_literal,
+                                uint32_t sampled_literal,
+                                uint32_t type_id,
+                                uint32_t coords_length,
+                                bool optional_operand) {
+    std::string type_str = R"(%1 = OpTypeFloat 32
+%2 = OpTypeInt 32 0
+%3 = OpTypeInt 32 1
+)";
+
+    type_str +=
+        texture_line(dim, unknown_format, type, depth_literal, sampled_literal);
+
+    type_str += R"(%5 = OpTypePointer Private %6
+%7 = OpConstantNull %6
+%4 = OpVariable %5 Private %7
+%10 = OpTypeSampler
+%9 = OpTypePointer Private %10
+%11 = OpConstantNull %10
+%8 = OpVariable %9 Private %11
+)";
+
+    if (coords_length > 1) {
+      type_str += "%14 = OpTypeVector %3 " + std::to_string(coords_length) +
+                  R"(
+%13 = OpTypePointer Private %14
+%15 = OpConstantNull %14
+%12 = OpVariable %13 Private %15
+)";
+
+    } else {
+      type_str += R"(%13 = OpTypePointer Private %3
+%14 = OpConstantNull %3
+%12 = OpVariable %13 Private %14
+)";
+    }
+
+    if (coords_length > 1 && optional_operand) {
+      type_str += R"(%17 = OpTypePointer Private %1
+%18 = OpConstantNull %1
+%16 = OpVariable %17 Private %18
+%20 = OpTypeVector %)" +
+                  std::to_string(type_id) + R"( 4
+%25 = OpTypeSampledImage %6
+)";
+    } else if (coords_length > 1 && !optional_operand) {
+      type_str += R"(%17 = OpTypeVector %)" + std::to_string(type_id) + R"( 4
+%21 = OpTypeSampledImage %6
+)";
+    } else if (coords_length == 1 && optional_operand) {
+      type_str += R"(%16 = OpTypePointer Private %1
+%17 = OpConstantNull %1
+%15 = OpVariable %16 Private %17
+%19 = OpTypeVector %)" +
+                  std::to_string(type_id) + R"( 4
+%24 = OpTypeSampledImage %6
+)";
+    } else {
+      type_str += R"(%16 = OpTypeVector %)" + std::to_string(type_id) + R"( 4
+%20 = OpTypeSampledImage %6
+)";
+    }
+
+    return type_str;
+  }
+
+  std::string generate_ops_str(uint32_t coords_length,
+                               std::string op_name,
+                               std::string optional_operand) {
+    if (coords_length > 1 && optional_operand == "") {
+      return R"(%18 = OpLoad %6 %4
+%19 = OpLoad %10 %8
+%20 = OpLoad %14 %12
+%22 = OpSampledImage %21 %18 %19
+%16 = )" + op_name +
+             R"( %17 %22 %20
+)";
+    }
+
+    if (coords_length == 1 && optional_operand == "") {
+      return R"(%17 = OpLoad %6 %4
+%18 = OpLoad %10 %8
+%19 = OpLoad %3 %12
+%21 = OpSampledImage %20 %17 %18
+%15 = )" + op_name +
+             R"( %16 %21 %19
+)";
+    }
+
+    if (coords_length > 1 && optional_operand != "") {
+      return R"(%21 = OpLoad %6 %4
+%22 = OpLoad %10 %8
+%23 = OpLoad %14 %12
+%24 = OpLoad %1 %16
+%26 = OpSampledImage %25 %21 %22
+%19 = )" + op_name +
+             " %20 %26 %23 " + optional_operand + " %24\n";
+    }
+
+    return R"(%20 = OpLoad %6 %4
+%21 = OpLoad %10 %8
+%22 = OpLoad %3 %12
+%23 = OpLoad %1 %15
+%25 = OpSampledImage %24 %20 %21
+%18 = )" + op_name +
+           " %19 %25 %22 " + optional_operand + " %23\n";
+  }
+};
+
+TEST_P(Builder_SampledTextureOperation, TextureSample) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+
+  ast::type::F32Type f32;
+  b()->GenerateTypeIfNeeded(&f32);
+  ast::type::U32Type u32;
+  b()->GenerateTypeIfNeeded(&u32);
+  ast::type::I32Type i32;
+  b()->GenerateTypeIfNeeded(&i32);
+
+  uint32_t type_id = 1;
+  if (type == TextureType::kU32) {
+    type_id = 2;
+  } else if (type == TextureType::kI32) {
+    type_id = 3;
+  }
+
+  auto coords_type = get_coords_type(dim, &i32);
+
+  uint32_t coords_length = 1;
+  if (coords_type->IsVector()) {
+    coords_length = coords_type->AsVector()->size();
+  }
+
+  auto sampler_type = std::make_unique<ast::type::SamplerType>(
+      ast::type::SamplerKind::kSampler);
+  std::unique_ptr<ast::type::Type> s = subtype(type);
+  auto* texture_type = ctx()->type_mgr().Get(
+      std::make_unique<ast::type::SampledTextureType>(dim, s.get()));
+
+  EXPECT_TRUE(td()->Determine());
+
+  ast::ExpressionList call_params;
+  b()->push_function(Function{});
+
+  add_call_param("texture", texture_type, &call_params);
+  add_call_param("sampler", sampler_type.get(), &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_sample"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  if (coords_length > 1) {
+    EXPECT_EQ(b()->GenerateExpression(&expr), 16u) << b()->error();
+  } else {
+    EXPECT_EQ(b()->GenerateExpression(&expr), 15u) << b()->error();
+  }
+
+  EXPECT_EQ(
+      DumpInstructions(b()->types()),
+      generate_type_str(dim, true, type, 0, 1, type_id, coords_length, false));
+
+  EXPECT_EQ(DumpInstructions(b()->functions()[0].instructions()),
+            generate_ops_str(coords_length, "OpImageSampleImplicitLod", ""));
+}
+
+TEST_P(Builder_SampledTextureOperation, TextureSampleLevel) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+
+  ast::type::F32Type f32;
+  b()->GenerateTypeIfNeeded(&f32);
+  ast::type::U32Type u32;
+  b()->GenerateTypeIfNeeded(&u32);
+  ast::type::I32Type i32;
+  b()->GenerateTypeIfNeeded(&i32);
+
+  uint32_t type_id = 1;
+  if (type == TextureType::kU32) {
+    type_id = 2;
+  } else if (type == TextureType::kI32) {
+    type_id = 3;
+  }
+
+  auto coords_type = get_coords_type(dim, &i32);
+
+  uint32_t coords_length = 1;
+  if (coords_type->IsVector()) {
+    coords_length = coords_type->AsVector()->size();
+  }
+
+  auto sampler_type = std::make_unique<ast::type::SamplerType>(
+      ast::type::SamplerKind::kSampler);
+  std::unique_ptr<ast::type::Type> s = subtype(type);
+  auto* texture_type = ctx()->type_mgr().Get(
+      std::make_unique<ast::type::SampledTextureType>(dim, s.get()));
+
+  EXPECT_TRUE(td()->Determine());
+
+  ast::ExpressionList call_params;
+  b()->push_function(Function{});
+
+  add_call_param("texture", texture_type, &call_params);
+  add_call_param("sampler", sampler_type.get(), &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+  add_call_param("lod", &f32, &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_sample_level"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  if (coords_length > 1) {
+    EXPECT_EQ(b()->GenerateExpression(&expr), 19u) << b()->error();
+  } else {
+    EXPECT_EQ(b()->GenerateExpression(&expr), 18u) << b()->error();
+  }
+
+  EXPECT_EQ(
+      DumpInstructions(b()->types()),
+      generate_type_str(dim, true, type, 0, 1, type_id, coords_length, true));
+
+  EXPECT_EQ(DumpInstructions(b()->functions()[0].instructions()),
+            generate_ops_str(coords_length, "OpImageSampleExplicitLod", "Lod"));
+}
+
+TEST_P(Builder_SampledTextureOperation, TextureSampleBias) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+
+  ast::type::F32Type f32;
+  b()->GenerateTypeIfNeeded(&f32);
+  ast::type::U32Type u32;
+  b()->GenerateTypeIfNeeded(&u32);
+  ast::type::I32Type i32;
+  b()->GenerateTypeIfNeeded(&i32);
+
+  uint32_t type_id = 1;
+  if (type == TextureType::kU32) {
+    type_id = 2;
+  } else if (type == TextureType::kI32) {
+    type_id = 3;
+  }
+
+  auto coords_type = get_coords_type(dim, &i32);
+
+  uint32_t coords_length = 1;
+  if (coords_type->IsVector()) {
+    coords_length = coords_type->AsVector()->size();
+  }
+
+  auto sampler_type = std::make_unique<ast::type::SamplerType>(
+      ast::type::SamplerKind::kSampler);
+  std::unique_ptr<ast::type::Type> s = subtype(type);
+  auto* texture_type = ctx()->type_mgr().Get(
+      std::make_unique<ast::type::SampledTextureType>(dim, s.get()));
+
+  EXPECT_TRUE(td()->Determine());
+
+  ast::ExpressionList call_params;
+  b()->push_function(Function{});
+
+  add_call_param("texture", texture_type, &call_params);
+  add_call_param("sampler", sampler_type.get(), &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+  add_call_param("bias", &f32, &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_sample_bias"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  if (coords_length > 1) {
+    EXPECT_EQ(b()->GenerateExpression(&expr), 19u) << b()->error();
+  } else {
+    EXPECT_EQ(b()->GenerateExpression(&expr), 18u) << b()->error();
+  }
+
+  EXPECT_EQ(
+      DumpInstructions(b()->types()),
+      generate_type_str(dim, true, type, 0, 1, type_id, coords_length, true));
+
+  EXPECT_EQ(
+      DumpInstructions(b()->functions()[0].instructions()),
+      generate_ops_str(coords_length, "OpImageSampleImplicitLod", "Bias"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    Builder_SampledTextureOperation,
+    testing::Values(
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k1d, TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k1dArray,
+                          TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k2d, TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k2dArray,
+                          TextureType::kU32},
+        TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kF32},
+        TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kI32},
+        TextureTestParams{ast::type::TextureDimension::k3d,
+                          TextureType::kU32}));
+
+class Builder_DepthTextureOperation : public Builder_TextureOperation {
+ public:
+  std::string generate_type_str(ast::type::TextureDimension dim,
+                                uint32_t coords_length) {
+    std::string type_str = R"(%1 = OpTypeFloat 32
+%2 = OpTypeInt 32 0
+%3 = OpTypeInt 32 1
+)";
+
+    type_str += texture_line(dim, true, TextureType::kF32, 1, 1);
+
+    type_str += R"(%5 = OpTypePointer Private %6
+%7 = OpConstantNull %6
+%4 = OpVariable %5 Private %7
+%10 = OpTypeSampler
+%9 = OpTypePointer Private %10
+%11 = OpConstantNull %10
+%8 = OpVariable %9 Private %11
+%14 = OpTypeVector %1 )" +
+                std::to_string(coords_length) + R"(
+%13 = OpTypePointer Private %14
+%15 = OpConstantNull %14
+%12 = OpVariable %13 Private %15
+%17 = OpTypePointer Private %1
+%18 = OpConstantNull %1
+%16 = OpVariable %17 Private %18
+%24 = OpTypeSampledImage %6
+%26 = OpConstant %1 0
+)";
+
+    return type_str;
+  }
+
+  std::string generate_ops_str() {
+    return R"(%20 = OpLoad %6 %4
+%21 = OpLoad %10 %8
+%22 = OpLoad %14 %12
+%23 = OpLoad %1 %16
+%25 = OpSampledImage %24 %20 %21
+%19 = OpImageSampleDrefExplicitLod %1 %25 %22 %23 Lod %26
+)";
+  }
+};
+
+TEST_P(Builder_DepthTextureOperation, TextureSampleCompare) {
+  auto dim = GetParam().dim;
+  auto type = GetParam().type;
+
+  ast::type::F32Type f32;
+  b()->GenerateTypeIfNeeded(&f32);
+  ast::type::U32Type u32;
+  b()->GenerateTypeIfNeeded(&u32);
+  ast::type::I32Type i32;
+  b()->GenerateTypeIfNeeded(&i32);
+
+  auto coords_type = get_coords_type(dim, &f32);
+
+  uint32_t coords_length = coords_type->AsVector()->size();
+
+  auto sampler_type = std::make_unique<ast::type::SamplerType>(
+      ast::type::SamplerKind::kComparisonSampler);
+  std::unique_ptr<ast::type::Type> s = subtype(type);
+  auto* texture_type =
+      ctx()->type_mgr().Get(std::make_unique<ast::type::DepthTextureType>(dim));
+
+  ast::ExpressionList call_params;
+  b()->push_function(Function{});
+
+  add_call_param("texture", texture_type, &call_params);
+  add_call_param("sampler", sampler_type.get(), &call_params);
+  add_call_param("coords", coords_type.get(), &call_params);
+  add_call_param("depth_reference", &f32, &call_params);
+
+  ast::CallExpression expr(
+      std::make_unique<ast::IdentifierExpression>("texture_sample_compare"),
+      std::move(call_params));
+
+  EXPECT_TRUE(td()->DetermineResultType(&expr));
+
+  EXPECT_EQ(b()->GenerateExpression(&expr), 19u) << b()->error();
+
+  EXPECT_EQ(DumpInstructions(b()->types()),
+            generate_type_str(dim, coords_length));
+
+  EXPECT_EQ(DumpInstructions(b()->functions()[0].instructions()),
+            generate_ops_str());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    BuilderTest,
+    Builder_DepthTextureOperation,
+    testing::Values(TextureTestParams{ast::type::TextureDimension::k2d},
+                    TextureTestParams{ast::type::TextureDimension::k2dArray},
+                    TextureTestParams{ast::type::TextureDimension::kCube},
+                    TextureTestParams{
+                        ast::type::TextureDimension::kCubeArray}));
+
+// This tests that we do not push OpTypeSampledImage and float_0 type twice.
+TEST_F(Builder_TextureOperation, TextureSampleCompareTwice) {
+  ast::type::F32Type f32;
+  b()->GenerateTypeIfNeeded(&f32);
+
+  auto coords_type = get_coords_type(ast::type::TextureDimension::k2d, &f32);
+  auto sampler_type = std::make_unique<ast::type::SamplerType>(
+      ast::type::SamplerKind::kComparisonSampler);
+  auto* texture_type =
+      ctx()->type_mgr().Get(std::make_unique<ast::type::DepthTextureType>(
+          ast::type::TextureDimension::k2d));
+
+  b()->push_function(Function{});
+
+  ast::ExpressionList call_params_first;
+  add_call_param("texture", texture_type, &call_params_first);
+  add_call_param("sampler", sampler_type.get(), &call_params_first);
+  add_call_param("coords", coords_type.get(), &call_params_first);
+  add_call_param("depth_reference", &f32, &call_params_first);
+
+  ast::ExpressionList call_params_second;
+  add_call_param("texture", texture_type, &call_params_second);
+  add_call_param("sampler", sampler_type.get(), &call_params_second);
+  add_call_param("coords", coords_type.get(), &call_params_second);
+  add_call_param("depth_reference", &f32, &call_params_second);
+
+  ast::CallExpression expr_first(
+      std::make_unique<ast::IdentifierExpression>("texture_sample_compare"),
+      std::move(call_params_first));
+  ast::CallExpression expr_second(
+      std::make_unique<ast::IdentifierExpression>("texture_sample_compare"),
+      std::move(call_params_second));
+
+  EXPECT_TRUE(td()->DetermineResultType(&expr_first));
+  EXPECT_TRUE(td()->DetermineResultType(&expr_second));
+
+  EXPECT_EQ(b()->GenerateExpression(&expr_first), 21u) << b()->error();
+  EXPECT_EQ(b()->GenerateExpression(&expr_second), 29u) << b()->error();
+
+  EXPECT_EQ(DumpInstructions(b()->types()), R"(%1 = OpTypeFloat 32
+%4 = OpTypeImage %1 2D 1 0 0 1 Unknown
+%3 = OpTypePointer Private %4
+%5 = OpConstantNull %4
+%2 = OpVariable %3 Private %5
+%8 = OpTypeSampler
+%7 = OpTypePointer Private %8
+%9 = OpConstantNull %8
+%6 = OpVariable %7 Private %9
+%12 = OpTypeVector %1 2
+%11 = OpTypePointer Private %12
+%13 = OpConstantNull %12
+%10 = OpVariable %11 Private %13
+%15 = OpTypePointer Private %1
+%16 = OpConstantNull %1
+%14 = OpVariable %15 Private %16
+%17 = OpVariable %3 Private %5
+%18 = OpVariable %7 Private %9
+%19 = OpVariable %11 Private %13
+%20 = OpVariable %15 Private %16
+%26 = OpTypeSampledImage %4
+%28 = OpConstant %1 0
+)");
+
+  EXPECT_EQ(DumpInstructions(b()->functions()[0].instructions()),
+            R"(%22 = OpLoad %4 %17
+%23 = OpLoad %8 %18
+%24 = OpLoad %12 %19
+%25 = OpLoad %1 %20
+%27 = OpSampledImage %26 %22 %23
+%21 = OpImageSampleDrefExplicitLod %1 %27 %24 %25 Lod %28
+%30 = OpLoad %4 %17
+%31 = OpLoad %8 %18
+%32 = OpLoad %12 %19
+%33 = OpLoad %1 %20
+%34 = OpSampledImage %26 %30 %31
+%29 = OpImageSampleDrefExplicitLod %1 %34 %32 %33 Lod %28
+)");
+}
+
 }  // namespace
 }  // namespace spirv
 }  // namespace writer