spirv-reader: Start emitting sampled image builtins

- Emit (non-depth) sampler variables
- Emit sampled texture variables

- Test emission of textureSample, textureBias, textureLevel

TODO: convert unsigned offset parameter to signed. crbug.com/tint/348
TODO: support arrayed access, where we have to split out the array index
into a separate operand. crbug.com/tint/349
TODO: for explicit-lod sampling, we may have to convert coordinates to
floating point. crbug.com/tint/346

Bug: tint:109
Change-Id: I12558f99473ca234ce0d09a87fc0c2f4730497bc
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/33342
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Auto-Submit: David Neto <dneto@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 57192d5..8276813 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -350,6 +350,44 @@
   return ast::Intrinsic::kNone;
 }
 
+// @param opcode a SPIR-V opcode
+// @returns true if the given instruction is an image access instruction
+// whose first input operand is an OpSampledImage value.
+bool IsSampledImageAccess(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpImageSampleImplicitLod:
+    case SpvOpImageSampleExplicitLod:
+    case SpvOpImageSampleDrefImplicitLod:
+    case SpvOpImageSampleDrefExplicitLod:
+    case SpvOpImageGather:
+    case SpvOpImageDrefGather:
+    case SpvOpImageQueryLod:
+      return true;
+    default:
+      // WGSL doesn't have *Proj* texturing.
+      break;
+  }
+  return false;
+}
+
+// @param opcode a SPIR-V opcode
+// @returns true if the given instruction is an image access instruction
+// whose first input operand is an OpImage value.
+bool IsRawImageAccess(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpImageRead:
+    case SpvOpImageWrite:
+    case SpvOpImageFetch:
+    case SpvOpImageQuerySizeLod:
+    case SpvOpImageQueryLevels:
+    case SpvOpImageQuerySamples:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
 // @returns the merge block ID for the given basic block, or 0 if there is none.
 uint32_t MergeFor(const spvtools::opt::BasicBlock& bb) {
   // Get the OpSelectionMerge or OpLoopMerge instruction, if any.
@@ -2597,27 +2635,37 @@
 
   // Handle combinatorial instructions.
   const auto* def_info = GetDefInfo(result_id);
-  auto combinatorial_expr = MaybeEmitCombinatorialValue(inst);
-  if (combinatorial_expr.expr != nullptr) {
-    if (def_info == nullptr) {
-      return Fail() << "internal error: result ID %" << result_id
-                    << " is missing a def_info";
+  if (def_info) {
+    if (def_info->skip_generation) {
+      return true;
     }
-    if (def_info->requires_hoisted_def || def_info->requires_named_const_def ||
-        def_info->num_uses != 1) {
-      // Generate a const definition or an assignment to a hoisted definition
-      // now and later use the const or variable name at the uses of this value.
-      return EmitConstDefOrWriteToHoistedVar(inst, combinatorial_expr);
+    auto combinatorial_expr = MaybeEmitCombinatorialValue(inst);
+    if (combinatorial_expr.expr != nullptr) {
+      if (def_info->requires_hoisted_def ||
+          def_info->requires_named_const_def || def_info->num_uses != 1) {
+        // Generate a const definition or an assignment to a hoisted definition
+        // now and later use the const or variable name at the uses of this
+        // value.
+        return EmitConstDefOrWriteToHoistedVar(inst, combinatorial_expr);
+      }
+      // It is harmless to defer emitting the expression until it's used.
+      // Any supporting statements have already been emitted.
+      singly_used_values_.insert(std::make_pair(result_id, combinatorial_expr));
+      return success();
     }
-    // It is harmless to defer emitting the expression until it's used.
-    // Any supporting statements have already been emitted.
-    singly_used_values_.insert(std::make_pair(result_id, combinatorial_expr));
-    return success();
   }
   if (failed()) {
     return false;
   }
 
+  if (IsSampledImageAccess(inst.opcode())) {
+    return EmitSampledImageAccess(inst);
+  }
+  if (IsRawImageAccess(inst.opcode())) {
+    return Fail() << "raw image access is not implemented yet:"
+                  << inst.PrettyPrint();
+  }
+
   switch (inst.opcode()) {
     case SpvOpNop:
       return true;
@@ -3195,34 +3243,45 @@
       }
       def_info_[result_id] = std::make_unique<DefInfo>(inst, block_pos, index);
       index++;
+      auto& info = def_info_[result_id];
 
       // Determine storage class for pointer values. Do this in order because
       // we might rely on the storage class for a previously-visited definition.
       // Logical pointers can't be transmitted through OpPhi, so remaining
       // pointer definitions are SSA values, and their definitions must be
       // visited before their uses.
-      auto& storage_class = def_info_[result_id]->storage_class;
       const auto* type = type_mgr_->GetType(inst.type_id());
-      if (type && type->AsPointer()) {
-        const auto* ast_type = parser_impl_.ConvertType(inst.type_id());
-        if (ast_type && ast_type->AsPointer()) {
-          storage_class = ast_type->AsPointer()->storage_class();
+      if (type) {
+        if (type->AsPointer()) {
+          const auto* ast_type = parser_impl_.ConvertType(inst.type_id());
+          if (ast_type && ast_type->AsPointer()) {
+            info->storage_class = ast_type->AsPointer()->storage_class();
+          }
+          switch (inst.opcode()) {
+            case SpvOpUndef:
+            case SpvOpVariable:
+              // Keep the default decision based on the result type.
+              break;
+            case SpvOpAccessChain:
+            case SpvOpCopyObject:
+              // Inherit from the first operand. We need this so we can pick up
+              // a remapped storage buffer.
+              info->storage_class = GetStorageClassForPointerValue(
+                  inst.GetSingleWordInOperand(0));
+              break;
+            default:
+              return Fail()
+                     << "pointer defined in function from unknown opcode: "
+                     << inst.PrettyPrint();
+          }
+          if (info->storage_class == ast::StorageClass::kUniformConstant) {
+            info->skip_generation = true;
+          }
         }
-        switch (inst.opcode()) {
-          case SpvOpUndef:
-          case SpvOpVariable:
-            // Keep the default decision based on the result type.
-            break;
-          case SpvOpAccessChain:
-          case SpvOpCopyObject:
-            // Inherit from the first operand. We need this so we can pick up
-            // a remapped storage buffer.
-            storage_class =
-                GetStorageClassForPointerValue(inst.GetSingleWordInOperand(0));
-            break;
-          default:
-            return Fail() << "pointer defined in function from unknown opcode: "
-                          << inst.PrettyPrint();
+        if (type->AsSampler() || type->AsImage() || type->AsSampledImage()) {
+          // Defer code generation until the instruction that actually acts on
+          // the image.
+          info->skip_generation = true;
         }
       }
     }
@@ -3563,6 +3622,99 @@
   }
 }
 
+bool FunctionEmitter::EmitSampledImageAccess(
+    const spvtools::opt::Instruction& inst) {
+  auto* result_type = parser_impl_.ConvertType(inst.type_id());
+
+  // The sampled image operand is always first.
+  const auto sampled_image_id = inst.GetSingleWordInOperand(0);
+  const auto* sampler =
+      parser_impl_.GetMemoryObjectDeclarationForHandle(sampled_image_id, false);
+  const auto* image =
+      parser_impl_.GetMemoryObjectDeclarationForHandle(sampled_image_id, true);
+
+  if (!sampler) {
+    return Fail() << "interal error: couldn't find sampler for "
+                  << inst.PrettyPrint();
+  }
+  if (!image) {
+    return Fail() << "interal error: couldn't find image for "
+                  << inst.PrettyPrint();
+  }
+
+  ast::ExpressionList params;
+  params.push_back(
+      create<ast::IdentifierExpression>(namer_.Name(image->result_id())));
+  params.push_back(
+      create<ast::IdentifierExpression>(namer_.Name(sampler->result_id())));
+
+  // Push the coordinates operand.
+  // TODO(dneto): For explicit-Lod variations, we may have to convert from
+  // integral coordinates to floating point coordinates.
+  // TODO(dneto): For arrayed access, split off the array layer.
+  params.push_back(MakeOperand(inst, 1).expr);
+  uint32_t arg_index = 2;
+
+  std::string builtin_name;
+  switch (inst.opcode()) {
+    case SpvOpImageSampleImplicitLod:
+    case SpvOpImageSampleExplicitLod:
+      builtin_name = "textureSample";
+      break;
+    case SpvOpImageGather:
+    case SpvOpImageDrefGather:
+      return Fail() << " image gather is not yet supported";
+    case SpvOpImageQueryLod:
+      return Fail() << " image query Lod is not yet supported";
+    default:
+      return Fail() << "internal error: sampled image access";
+  }
+
+  // Loop over the image operands, looking for extra operands to the builtin.
+  // Except we uroll the loop.
+  const auto num_args = inst.NumInOperands();
+  uint32_t image_operands_mask = 0;
+  if (arg_index < num_args) {
+    image_operands_mask = inst.GetSingleWordInOperand(arg_index);
+    arg_index++;
+  }
+  if (arg_index < num_args &&
+      (image_operands_mask & SpvImageOperandsBiasMask)) {
+    builtin_name += "Bias";
+    params.push_back(MakeOperand(inst, arg_index).expr);
+    image_operands_mask ^= SpvImageOperandsBiasMask;
+    arg_index++;
+  }
+  if (arg_index < num_args && (image_operands_mask & SpvImageOperandsLodMask)) {
+    builtin_name += "Level";
+    params.push_back(MakeOperand(inst, arg_index).expr);
+    image_operands_mask ^= SpvImageOperandsLodMask;
+    arg_index++;
+  }
+  if (arg_index + 1 < num_args &&
+      (image_operands_mask & SpvImageOperandsGradMask)) {
+    builtin_name += "Grad";
+    params.push_back(MakeOperand(inst, arg_index).expr);
+    params.push_back(MakeOperand(inst, arg_index + 1).expr);
+    image_operands_mask ^= SpvImageOperandsGradMask;
+    arg_index += 2;
+  }
+  if (arg_index < num_args &&
+      (image_operands_mask & SpvImageOperandsConstOffsetMask)) {
+    params.push_back(MakeOperand(inst, arg_index).expr);
+    image_operands_mask ^= SpvImageOperandsConstOffsetMask;
+    arg_index++;
+  }
+  if (image_operands_mask) {
+    return Fail() << "unsupported image operands (" << image_operands_mask
+                  << "): " << inst.PrettyPrint();
+  }
+
+  auto* ident = create<ast::IdentifierExpression>(builtin_name);
+  auto* call_expr = create<ast::CallExpression>(ident, std::move(params));
+  return EmitConstDefOrWriteToHoistedVar(inst, {result_type, call_expr});
+}
+
 }  // namespace spirv
 }  // namespace reader
 }  // namespace tint
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index 3211dc9..cfebe80 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -259,6 +259,17 @@
   /// that needs to be remapped to StorageBuffer storage class.
   /// This is kNone for non-pointers.
   ast::StorageClass storage_class = ast::StorageClass::kNone;
+
+  /// Should this instruction be skipped when generating code?
+  /// This is true for any intermediate value which is an sampler, image,
+  /// or sampled image, or any pointer to such object. Code is generated
+  /// for those objects only when emitting the image instructions that access
+  /// the image (read, write, sample, gather, fetch, or query). For example,
+  /// when encountering an OpImageSampleExplicitLod, a call to the
+  /// textureSampleLevel builtin function will be emitted, and the call will
+  /// directly reference the underlying texture and sampler (variable or
+  /// function parameter).
+  bool skip_generation = false;
 };
 
 inline std::ostream& operator<<(std::ostream& o, const DefInfo& di) {
@@ -693,6 +704,12 @@
   /// @returns an expression
   TypedExpression MakeIntrinsicCall(const spvtools::opt::Instruction& inst);
 
+  /// Emits a texture builtin function call for a SPIR-V instruction that
+  /// accesses a sampled image.
+  /// @param inst the SPIR-V instruction
+  /// @returns an expression
+  bool EmitSampledImageAccess(const spvtools::opt::Instruction& inst);
+
   /// Returns an expression for an OpSelect, if its operands are scalars
   /// or vectors. These translate directly to WGSL select.  Otherwise, return
   /// an expression with a null owned expression
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 4c7e867..4ea1e21 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1102,16 +1102,26 @@
     if (!success_) {
       return false;
     }
-    auto* ast_type = id_to_type_[type_id];
-    if (ast_type == nullptr) {
-      return Fail() << "internal error: failed to register Tint AST type for "
-                       "SPIR-V type with ID: "
-                    << var.type_id();
+    ast::type::Type* ast_type = nullptr;
+    if (spirv_storage_class == SpvStorageClassUniformConstant) {
+      // These are opaque handles: samplers or textures
+      ast_type = GetTypeForHandleVar(var);
+      if (!ast_type) {
+        return false;
+      }
+    } else {
+      ast_type = id_to_type_[type_id];
+      if (ast_type == nullptr) {
+        return Fail() << "internal error: failed to register Tint AST type for "
+                         "SPIR-V type with ID: "
+                      << var.type_id();
+      }
+      if (!ast_type->IsPointer()) {
+        return Fail() << "variable with ID " << var.result_id()
+                      << " has non-pointer type " << var.type_id();
+      }
     }
-    if (!ast_type->IsPointer()) {
-      return Fail() << "variable with ID " << var.result_id()
-                    << " has non-pointer type " << var.type_id();
-    }
+
     auto* ast_store_type = ast_type->AsPointer()->type();
     auto ast_storage_class = ast_type->AsPointer()->storage_class();
     auto* ast_var =
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
index 1d28d1d..06109be 100644
--- a/src/reader/spirv/parser_impl_handle_test.cc
+++ b/src/reader/spirv/parser_impl_handle_test.cc
@@ -48,10 +48,13 @@
     %uint = OpTypeInt 32 0
     %int = OpTypeInt 32 1
 
+    %int_3 = OpConstant %uint 3
+    %int_4 = OpConstant %uint 4
     %uint_1 = OpConstant %uint 1
     %uint_2 = OpConstant %uint 2
     %uint_100 = OpConstant %uint 100
 
+    %v2int = OpTypeVector %int 2
     %v2uint = OpTypeVector %uint 2
     %v4uint = OpTypeVector %uint 4
     %v4int = OpTypeVector %int 4
@@ -60,11 +63,13 @@
     %v4float = OpTypeVector %float 4
 
     %float_null = OpConstantNull %float
+    %float_7 = OpConstant %float 7
     %v2float_null = OpConstantNull %v2float
     %v3float_null = OpConstantNull %v3float
     %v4float_null = OpConstantNull %v4float
 
     %depth = OpConstant %float 0.2
+    %offsets2d = OpConstantComposite %v2int %int_3 %int_4
 
 ; Define types for all sampler and texture types that can map to WGSL,
 ; modulo texel formats for storage textures. For now, we limit
@@ -1054,6 +1059,472 @@
                           "%uint %im",
                           "Usage(Texture( is_sampled ms ))"}));
 
+// Test emission of handle variables.
+
+struct DeclSampledImageCase {
+  std::string inst;             // The provoking image access instruction.
+  std::string var_decl;         // WGSL variable declaration
+  std::string texture_builtin;  // WGSL texture usage.
+};
+inline std::ostream& operator<<(std::ostream& out,
+                                const DeclSampledImageCase& c) {
+  out << "UsageSampledImageCase(" << c.inst << "\n"
+      << c.var_decl << "\n"
+      << c.texture_builtin << ")";
+  return out;
+}
+
+using SpvParserTest_DeclHandle_SampledImage =
+    SpvParserTestBase<::testing::TestWithParam<DeclSampledImageCase>>;
+
+TEST_P(SpvParserTest_DeclHandle_SampledImage, Variable) {
+  const auto assembly = Preamble() + R"(
+     OpDecorate %10 DescriptorSet 0
+     OpDecorate %10 Binding 0
+     OpDecorate %20 DescriptorSet 2
+     OpDecorate %20 Binding 1
+)" + CommonTypes() + R"(
+     %si_ty = OpTypeSampledImage %f_texture_2d
+     %coords = OpConstantNull %v2float
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_2d UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %sam = OpLoad %sampler %10
+     %im = OpLoad %f_texture_2d %20
+     %sampled_image = OpSampledImage %si_ty %im %sam
+)" + GetParam().inst + R"(
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+  EXPECT_TRUE(p->error().empty()) << p->error();
+  const auto module = p->module().to_str();
+  EXPECT_THAT(module, HasSubstr(GetParam().var_decl))
+      << "DECLARATIONS ARE BAD " << module;
+  EXPECT_THAT(module, HasSubstr(GetParam().texture_builtin))
+      << "TEXTURE BUILTIN IS BAD " << module << assembly;
+}
+
+// TODO(dneto): Test variable declaration and texture builtins provoked by
+// use of an image access instruction inside helper function.
+TEST_P(SpvParserTest_RegisterHandleUsage_SampledImage, DISABLED_FunctionParam) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %f_ty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_2d
+     %si_ty = OpTypeSampledImage %f_texture_2d
+     %coords = OpConstantNull %v2float
+     %component = OpConstant %uint 1
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_2d UniformConstant
+
+     %func = OpFunction %void None %f_ty
+     %110 = OpFunctionParameter %ptr_sampler
+     %120 = OpFunctionParameter %ptr_f_texture_2d
+     %func_entry = OpLabel
+     %sam = OpLoad %sampler %110
+     %im = OpLoad %f_texture_2d %120
+     %sampled_image = OpSampledImage %si_ty %im %sam
+
+)" + GetParam().inst + R"(
+
+     OpReturn
+     OpFunctionEnd
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+     %foo = OpFunctionCall %void %func %10 %20
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule()) << p->error() << assembly << std::endl;
+  EXPECT_TRUE(p->RegisterHandleUsage()) << p->error() << assembly << std::endl;
+  EXPECT_TRUE(p->error().empty()) << p->error() << assembly << std::endl;
+  Usage su = p->GetHandleUsage(10);
+  Usage iu = p->GetHandleUsage(20);
+
+  EXPECT_THAT(su.to_str(), Eq(GetParam().expected_sampler_usage));
+  EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    DISABLED_ImageGather,
+    SpvParserTest_DeclHandle_SampledImage,
+    ::testing::ValuesIn(std::vector<DeclSampledImageCase>{
+        // TODO(dneto): OpImageGather
+        // TODO(dneto): OpImageGather with ConstOffset (signed and unsigned)
+        // TODO(dneto): OpImageGather with Offset (signed and unsigned)
+        // TODO(dneto): OpImageGather with Offsets (signed and unsigned)
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    DISABLED_ImageDrefGather,
+    SpvParserTest_DeclHandle_SampledImage,
+    ::testing::ValuesIn(std::vector<DeclSampledImageCase>{
+        // TODO(dneto): OpImageDrefGather
+        // TODO(dneto): OpImageDrefGather with ConstOffset (signed and
+        // unsigned)
+        // TODO(dneto): OpImageDrefGather with Offset (signed and unsigned)
+        // TODO(dneto): OpImageDrefGather with Offsets (signed and unsigned)
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    ImageSampleImplicitLod,
+    SpvParserTest_DeclHandle_SampledImage,
+    ::testing::Values(
+
+        // OpImageSampleImplicitLod
+        DeclSampledImageCase{"%result = OpImageSampleImplicitLod "
+                             "%v4float %sampled_image %coords",
+                             R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+                             R"(
+          Call[not set]{
+            Identifier[not set]{textureSample}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{x_10}
+              TypeConstructor[not set]{
+                __vec_2__f32
+                ScalarConstructor[not set]{0.000000}
+                ScalarConstructor[not set]{0.000000}
+              }
+            )
+          })"},
+
+        // OpImageSampleImplicitLod with ConstOffset
+        DeclSampledImageCase{
+            "%result = OpImageSampleImplicitLod "
+            "%v4float %sampled_image %coords ConstOffset %offsets2d",
+            R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+            R"(
+          Call[not set]{
+            Identifier[not set]{textureSample}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{x_10}
+              TypeConstructor[not set]{
+                __vec_2__f32
+                ScalarConstructor[not set]{0.000000}
+                ScalarConstructor[not set]{0.000000}
+              }
+              TypeConstructor[not set]{
+                __vec_2__i32
+                ScalarConstructor[not set]{3}
+                ScalarConstructor[not set]{4}
+              }
+            )
+          })"},
+
+        // OpImageSampleImplicitLod with Bias
+        DeclSampledImageCase{"%result = OpImageSampleImplicitLod "
+                             "%v4float %sampled_image %coords Bias %float_7",
+                             R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+                             R"(
+          Call[not set]{
+            Identifier[not set]{textureSampleBias}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{x_10}
+              TypeConstructor[not set]{
+                __vec_2__f32
+                ScalarConstructor[not set]{0.000000}
+                ScalarConstructor[not set]{0.000000}
+              }
+              ScalarConstructor[not set]{7.000000}
+            )
+          })"},
+
+        // OpImageSampleImplicitLod with Bias and ConstOffset
+        // TODO(dneto): OpImageSampleImplicitLod with Bias and unsigned
+        // ConstOffset
+        DeclSampledImageCase{"%result = OpImageSampleImplicitLod "
+                             "%v4float %sampled_image %coords Bias|ConstOffset "
+                             "%float_7 %offsets2d",
+                             R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+                             R"(
+          Call[not set]{
+            Identifier[not set]{textureSampleBias}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{x_10}
+              TypeConstructor[not set]{
+                __vec_2__f32
+                ScalarConstructor[not set]{0.000000}
+                ScalarConstructor[not set]{0.000000}
+              }
+              ScalarConstructor[not set]{7.000000}
+              TypeConstructor[not set]{
+                __vec_2__i32
+                ScalarConstructor[not set]{3}
+                ScalarConstructor[not set]{4}
+              }
+            )
+          })"}
+
+        ));
+
+INSTANTIATE_TEST_SUITE_P(
+    DISABLED_ImageSampleDrefImplicitLod,
+    SpvParserTest_DeclHandle_SampledImage,
+    ::testing::ValuesIn(std::vector<DeclSampledImageCase>{
+        // TODO(dneto): ImageSampleDrefImplicitLod
+        // TODO(dneto): ImageSampleDrefImplicitLod with ConstOffset (signed and
+        // unsigned)
+        // TODO(dneto): ImageSampleDrefImplicitLod with Bias
+        // TODO(dneto): ImageSampleDrefImplicitLod with Biase and ConstOffset
+        // (signed and unsigned)
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    DisabledimageSampleExplicitLod,
+    SpvParserTest_DeclHandle_SampledImage,
+    ::testing::Values(
+
+        // OpImageSampleExplicitLod - using Lod
+        DeclSampledImageCase{"%result = OpImageSampleExplicitLod "
+                             "%v4float %sampled_image %coords Lod %float_null",
+                             R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+                             R"(
+          Call[not set]{
+            Identifier[not set]{textureSampleLevel}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{x_10}
+              TypeConstructor[not set]{
+                __vec_2__f32
+                ScalarConstructor[not set]{0.000000}
+                ScalarConstructor[not set]{0.000000}
+              }
+              ScalarConstructor[not set]{0.000000}
+            )
+          })"},
+
+        // OpImageSampleExplicitLod - using Lod and ConstOffset
+        // TODO(dneto) OpImageSampleExplicitLod - using Lod and unsigned
+        // ConstOffset
+        DeclSampledImageCase{"%result = OpImageSampleExplicitLod "
+                             "%v4float %sampled_image %coords Lod|ConstOffset "
+                             "%float_null %offsets2d",
+                             R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+                             R"(
+          Call[not set]{
+            Identifier[not set]{textureSampleLevel}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{x_10}
+              TypeConstructor[not set]{
+                __vec_2__f32
+                ScalarConstructor[not set]{0.000000}
+                ScalarConstructor[not set]{0.000000}
+              }
+              ScalarConstructor[not set]{0.000000}
+              TypeConstructor[not set]{
+                __vec_2__i32
+                ScalarConstructor[not set]{3}
+                ScalarConstructor[not set]{4}
+              }
+            )
+          })"},
+
+        // OpImageSampleExplicitLod - using Grad
+        DeclSampledImageCase{
+            "%result = OpImageSampleExplicitLod "
+            "%v4float %sampled_image %coords Grad %float_7 %float_null",
+            R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+            R"(
+          Call[not set]{
+            Identifier[not set]{textureSampleGrad}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{x_10}
+              TypeConstructor[not set]{
+                __vec_2__f32
+                ScalarConstructor[not set]{0.000000}
+                ScalarConstructor[not set]{0.000000}
+              }
+              ScalarConstructor[not set]{7.000000}
+              ScalarConstructor[not set]{0.000000}
+            )
+          })"},
+
+        // OpImageSampleExplicitLod - using Grad and ConstOffset
+        // TODO(dneto): OpImageSampleExplicitLod - using Grad and unsigned
+        // ConstOffset
+        DeclSampledImageCase{"%result = OpImageSampleExplicitLod "
+                             "%v4float %sampled_image %coords Grad|ConstOffset "
+                             "%float_7 %float_null %offsets2d",
+                             R"(
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{0}
+      BindingDecoration{0}
+    }
+    x_10
+    uniform_constant
+    __sampler_sampler
+  }
+  DecoratedVariable{
+    Decorations{
+      SetDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __sampled_texture_2d__f32
+  })",
+                             R"(
+          Call[not set]{
+            Identifier[not set]{textureSampleGrad}
+            (
+              Identifier[not set]{x_20}
+              Identifier[not set]{x_10}
+              TypeConstructor[not set]{
+                __vec_2__f32
+                ScalarConstructor[not set]{0.000000}
+                ScalarConstructor[not set]{0.000000}
+              }
+              ScalarConstructor[not set]{7.000000}
+              ScalarConstructor[not set]{0.000000}
+              TypeConstructor[not set]{
+                __vec_2__i32
+                ScalarConstructor[not set]{3}
+                ScalarConstructor[not set]{4}
+              }
+            )
+          })"}));
+
 }  // namespace
 }  // namespace spirv
 }  // namespace reader