Add validator checks for sparse image opcodes
authorAndrey Tuganov <andreyt@google.com>
Fri, 8 Dec 2017 22:57:12 +0000 (17:57 -0500)
committerDavid Neto <dneto@google.com>
Tue, 12 Dec 2017 17:04:23 +0000 (12:04 -0500)
source/validate_image.cpp
test/val/val_image_test.cpp

index ffd4106..9bc2632 100644 (file)
@@ -196,7 +196,8 @@ uint32_t GetPlaneCoordSize(const ImageTypeInfo& info) {
 // the instruction uses projection coordinates.
 uint32_t GetMinCoordSize(SpvOp opcode, const ImageTypeInfo& info) {
   if (info.dim == SpvDimCube &&
-      (opcode == SpvOpImageRead || opcode == SpvOpImageWrite)) {
+      (opcode == SpvOpImageRead || opcode == SpvOpImageWrite ||
+       opcode == SpvOpImageSparseRead)) {
     // These opcodes use UV for Cube, not direction vector.
     return 3;
   }
@@ -270,7 +271,8 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
   }
 
   if (mask & SpvImageOperandsLodMask) {
-    if (!is_explicit_lod && opcode != SpvOpImageFetch) {
+    if (!is_explicit_lod && opcode != SpvOpImageFetch &&
+        opcode != SpvOpImageSparseFetch) {
       return _.diag(SPV_ERROR_INVALID_DATA)
              << "Image Operand Lod can only be used with ExplicitLod opcodes "
              << "and OpImageFetch: " << spvOpcodeString(opcode);
@@ -287,14 +289,14 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
     if (is_explicit_lod) {
       if (!_.IsFloatScalarType(type_id)) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-            << "Expected Image Operand Lod to be float scalar when used with "
-            << "ExplicitLod: " << spvOpcodeString(opcode);
+               << "Expected Image Operand Lod to be float scalar when used "
+               << "with ExplicitLod: " << spvOpcodeString(opcode);
       }
     } else {
       if (!_.IsIntScalarType(type_id)) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-            << "Expected Image Operand Lod to be int scalar when used with "
-            << "OpImageFetch";
+               << "Expected Image Operand Lod to be int scalar when used with "
+               << "OpImageFetch";
       }
     }
 
@@ -411,7 +413,9 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
   }
 
   if (mask & SpvImageOperandsConstOffsetsMask) {
-    if (opcode != SpvOpImageGather && opcode != SpvOpImageDrefGather) {
+    if (opcode != SpvOpImageGather && opcode != SpvOpImageDrefGather &&
+        opcode != SpvOpImageSparseGather &&
+        opcode != SpvOpImageSparseDrefGather) {
       return _.diag(SPV_ERROR_INVALID_DATA)
              << "Image Operand ConstOffsets can only be used with "
                 "OpImageGather "
@@ -544,7 +548,8 @@ spv_result_t ValidateImageCommon(ValidationState_t& _,
     }
   }
 
-  if (opcode == SpvOpImageRead || opcode == SpvOpImageWrite) {
+  if (opcode == SpvOpImageRead || opcode == SpvOpImageSparseRead ||
+      opcode == SpvOpImageWrite) {
     if (info.sampled == 0) {
     } else if (info.sampled == 2) {
       if (info.dim == SpvDim1D && !_.HasCapability(SpvCapabilityImage1D)) {
@@ -589,6 +594,73 @@ spv_result_t ValidateImageCommon(ValidationState_t& _,
   return SPV_SUCCESS;
 }
 
+// Returns true if opcode is *ImageSparse*, false otherwise.
+bool IsSparse(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpImageSparseSampleImplicitLod:
+    case SpvOpImageSparseSampleExplicitLod:
+    case SpvOpImageSparseSampleDrefImplicitLod:
+    case SpvOpImageSparseSampleDrefExplicitLod:
+    case SpvOpImageSparseSampleProjImplicitLod:
+    case SpvOpImageSparseSampleProjExplicitLod:
+    case SpvOpImageSparseSampleProjDrefImplicitLod:
+    case SpvOpImageSparseSampleProjDrefExplicitLod:
+    case SpvOpImageSparseFetch:
+    case SpvOpImageSparseGather:
+    case SpvOpImageSparseDrefGather:
+    case SpvOpImageSparseTexelsResident:
+    case SpvOpImageSparseRead: {
+      return true;
+    }
+
+    default: { return false; }
+  }
+
+  return false;
+}
+
+// Checks sparse image opcode result type and returns the second struct member.
+// Returns inst.type_id for non-sparse image opcodes.
+// Not valid for sparse image opcodes which do not return a struct.
+spv_result_t GetActualResultType(ValidationState_t& _,
+                                 const spv_parsed_instruction_t& inst,
+                                 uint32_t* actual_result_type) {
+  const SpvOp opcode = static_cast<SpvOp>(inst.opcode);
+
+  if (IsSparse(opcode)) {
+    const Instruction* const type_inst = _.FindDef(inst.type_id);
+    assert(type_inst);
+
+    if (!type_inst || type_inst->opcode() != SpvOpTypeStruct) {
+      return _.diag(SPV_ERROR_INVALID_DATA)
+             << spvOpcodeString(opcode)
+             << ": expected Result Type to be OpTypeStruct";
+    }
+
+    if (type_inst->words().size() != 4 ||
+        !_.IsIntScalarType(type_inst->word(2))) {
+      return _.diag(SPV_ERROR_INVALID_DATA)
+             << spvOpcodeString(opcode)
+             << ": expected Result Type to be a struct containing an int "
+                "scalar "
+             << "and a texel";
+    }
+
+    *actual_result_type = type_inst->word(3);
+  } else {
+    *actual_result_type = inst.type_id;
+  }
+
+  return SPV_SUCCESS;
+}
+
+// Returns a string describing actual result type of an opcode.
+// Not valid for sparse image opcodes which do not return a struct.
+const char* GetActualResultTypeStr(SpvOp opcode) {
+  if (IsSparse(opcode)) return "Result Type's second member";
+  return "Result Type";
+}
+
 }  // namespace
 
 // Validates correctness of image instructions.
@@ -610,7 +682,7 @@ spv_result_t ImagePass(ValidationState_t& _,
       ImageTypeInfo info;
       if (!GetImageTypeInfo(_, inst->words[1], &info)) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-            << "OpTypeImage: corrupt definition";
+               << "OpTypeImage: corrupt definition";
       }
 
       const SpvOp sampled_type_opcode = _.GetIdOpcode(info.sampled_type);
@@ -618,48 +690,48 @@ spv_result_t ImagePass(ValidationState_t& _,
           sampled_type_opcode != SpvOpTypeInt &&
           sampled_type_opcode != SpvOpTypeFloat) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-            << spvOpcodeString(opcode)
-            << ": expected Sampled Type to be either void or numerical scalar "
-            << "type";
+               << spvOpcodeString(opcode)
+               << ": expected Sampled Type to be either void or numerical "
+               << "scalar type";
       }
 
       // Dim is checked elsewhere.
 
       if (info.depth > 2) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-            << spvOpcodeString(opcode)
-            << ": invalid Depth " << info.depth << " (must be 0, 1 or 2)";
+               << spvOpcodeString(opcode) << ": invalid Depth " << info.depth
+               << " (must be 0, 1 or 2)";
       }
 
       if (info.arrayed > 1) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-            << spvOpcodeString(opcode)
-            << ": invalid Arrayed " << info.arrayed << " (must be 0 or 1)";
+               << spvOpcodeString(opcode) << ": invalid Arrayed "
+               << info.arrayed << " (must be 0 or 1)";
       }
 
       if (info.multisampled > 1) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-            << spvOpcodeString(opcode)
-            << ": invalid MS " << info.multisampled << " (must be 0 or 1)";
+               << spvOpcodeString(opcode) << ": invalid MS "
+               << info.multisampled << " (must be 0 or 1)";
       }
 
       if (info.sampled > 2) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-            << spvOpcodeString(opcode)
-            << ": invalid Sampled " << info.sampled << " (must be 0, 1 or 2)";
+               << spvOpcodeString(opcode) << ": invalid Sampled "
+               << info.sampled << " (must be 0, 1 or 2)";
       }
 
       if (info.dim == SpvDimSubpassData) {
         if (info.sampled != 2) {
           return _.diag(SPV_ERROR_INVALID_DATA)
-            << spvOpcodeString(opcode)
-            << ": Dim SubpassData requires Sampled to be 2";
+                 << spvOpcodeString(opcode)
+                 << ": Dim SubpassData requires Sampled to be 2";
         }
 
         if (info.format != SpvImageFormatUnknown) {
           return _.diag(SPV_ERROR_INVALID_DATA)
-              << spvOpcodeString(opcode)
-              << ": Dim SubpassData requires format Unknown";
+                 << spvOpcodeString(opcode)
+                 << ": Dim SubpassData requires format Unknown";
         }
       }
 
@@ -672,8 +744,8 @@ spv_result_t ImagePass(ValidationState_t& _,
       const uint32_t image_type = inst->words[2];
       if (_.GetIdOpcode(image_type) != SpvOpTypeImage) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-            << spvOpcodeString(opcode)
-            << ": expected Image to be of type OpTypeImage";
+               << spvOpcodeString(opcode)
+               << ": expected Image to be of type OpTypeImage";
       }
 
       break;
@@ -726,18 +798,27 @@ spv_result_t ImagePass(ValidationState_t& _,
     case SpvOpImageSampleImplicitLod:
     case SpvOpImageSampleExplicitLod:
     case SpvOpImageSampleProjImplicitLod:
-    case SpvOpImageSampleProjExplicitLod: {
-      if (!_.IsIntVectorType(result_type) &&
-          !_.IsFloatVectorType(result_type)) {
+    case SpvOpImageSampleProjExplicitLod:
+    case SpvOpImageSparseSampleImplicitLod:
+    case SpvOpImageSparseSampleExplicitLod: {
+      uint32_t actual_result_type = 0;
+      if (spv_result_t error =
+              GetActualResultType(_, *inst, &actual_result_type)) {
+        return error;
+      }
+
+      if (!_.IsIntVectorType(actual_result_type) &&
+          !_.IsFloatVectorType(actual_result_type)) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-               << "Expected Result Type to be int or float vector type: "
+               << "Expected " << GetActualResultTypeStr(opcode)
+               << " to be int or float vector type: "
                << spvOpcodeString(opcode);
       }
 
-      if (_.GetDimension(result_type) != 4) {
+      if (_.GetDimension(actual_result_type) != 4) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-               << "Expected Result Type to have 4 components: "
-               << spvOpcodeString(opcode);
+               << "Expected " << GetActualResultTypeStr(opcode)
+               << " to have 4 components: " << spvOpcodeString(opcode);
       }
 
       const uint32_t image_type = _.GetOperandTypeId(inst, 2);
@@ -757,17 +838,19 @@ spv_result_t ImagePass(ValidationState_t& _,
         return result;
 
       if (_.GetIdOpcode(info.sampled_type) != SpvOpTypeVoid) {
-        const uint32_t result_component_type = _.GetComponentType(result_type);
-        if (result_component_type != info.sampled_type) {
+        const uint32_t texel_component_type =
+            _.GetComponentType(actual_result_type);
+        if (texel_component_type != info.sampled_type) {
           return _.diag(SPV_ERROR_INVALID_DATA)
-                 << "Expected Image 'Sampled Type' to be the same as Result "
-                    "Type "
-                 << "components: " << spvOpcodeString(opcode);
+                 << "Expected Image 'Sampled Type' to be the same as "
+                 << GetActualResultTypeStr(opcode)
+                 << " components: " << spvOpcodeString(opcode);
         }
       }
 
       const uint32_t coord_type = _.GetOperandTypeId(inst, 3);
-      if (opcode == SpvOpImageSampleExplicitLod &&
+      if ((opcode == SpvOpImageSampleExplicitLod ||
+           opcode == SpvOpImageSparseSampleExplicitLod) &&
           _.HasCapability(SpvCapabilityKernel)) {
         if (!_.IsFloatScalarOrVectorType(coord_type) &&
             !_.IsIntScalarOrVectorType(coord_type)) {
@@ -808,11 +891,20 @@ spv_result_t ImagePass(ValidationState_t& _,
     case SpvOpImageSampleDrefImplicitLod:
     case SpvOpImageSampleDrefExplicitLod:
     case SpvOpImageSampleProjDrefImplicitLod:
-    case SpvOpImageSampleProjDrefExplicitLod: {
-      if (!_.IsIntScalarType(result_type) &&
-          !_.IsFloatScalarType(result_type)) {
+    case SpvOpImageSampleProjDrefExplicitLod:
+    case SpvOpImageSparseSampleDrefImplicitLod:
+    case SpvOpImageSparseSampleDrefExplicitLod: {
+      uint32_t actual_result_type = 0;
+      if (spv_result_t error =
+              GetActualResultType(_, *inst, &actual_result_type)) {
+        return error;
+      }
+
+      if (!_.IsIntScalarType(actual_result_type) &&
+          !_.IsFloatScalarType(actual_result_type)) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-               << "Expected Result Type to be int or float scalar type: "
+               << "Expected " << GetActualResultTypeStr(opcode)
+               << " to be int or float scalar type: "
                << spvOpcodeString(opcode);
       }
 
@@ -832,10 +924,10 @@ spv_result_t ImagePass(ValidationState_t& _,
       if (spv_result_t result = ValidateImageCommon(_, *inst, info))
         return result;
 
-      if (result_type != info.sampled_type) {
+      if (actual_result_type != info.sampled_type) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-               << "Expected Image 'Sampled Type' to be the same as Result "
-                  "Type: "
+               << "Expected Image 'Sampled Type' to be the same as "
+               << GetActualResultTypeStr(opcode) << ": "
                << spvOpcodeString(opcode);
       }
 
@@ -875,18 +967,26 @@ spv_result_t ImagePass(ValidationState_t& _,
       break;
     }
 
-    case SpvOpImageFetch: {
-      if (!_.IsIntVectorType(result_type) &&
-          !_.IsFloatVectorType(result_type)) {
+    case SpvOpImageFetch:
+    case SpvOpImageSparseFetch: {
+      uint32_t actual_result_type = 0;
+      if (spv_result_t error =
+              GetActualResultType(_, *inst, &actual_result_type)) {
+        return error;
+      }
+
+      if (!_.IsIntVectorType(actual_result_type) &&
+          !_.IsFloatVectorType(actual_result_type)) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-               << "Expected Result Type to be int or float vector type: "
+               << "Expected " << GetActualResultTypeStr(opcode)
+               << " to be int or float vector type: "
                << spvOpcodeString(opcode);
       }
 
-      if (_.GetDimension(result_type) != 4) {
+      if (_.GetDimension(actual_result_type) != 4) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-               << "Expected Result Type to have 4 components: "
-               << spvOpcodeString(opcode);
+               << "Expected " << GetActualResultTypeStr(opcode)
+               << " to have 4 components: " << spvOpcodeString(opcode);
       }
 
       const uint32_t image_type = _.GetOperandTypeId(inst, 2);
@@ -903,12 +1003,13 @@ spv_result_t ImagePass(ValidationState_t& _,
       }
 
       if (_.GetIdOpcode(info.sampled_type) != SpvOpTypeVoid) {
-        const uint32_t result_component_type = _.GetComponentType(result_type);
+        const uint32_t result_component_type =
+            _.GetComponentType(actual_result_type);
         if (result_component_type != info.sampled_type) {
           return _.diag(SPV_ERROR_INVALID_DATA)
-                 << "Expected Image 'Sampled Type' to be the same as Result "
-                    "Type "
-                 << "components: " << spvOpcodeString(opcode);
+                 << "Expected Image 'Sampled Type' to be the same as "
+                 << GetActualResultTypeStr(opcode)
+                 << " components: " << spvOpcodeString(opcode);
         }
       }
 
@@ -950,18 +1051,27 @@ spv_result_t ImagePass(ValidationState_t& _,
     }
 
     case SpvOpImageGather:
-    case SpvOpImageDrefGather: {
-      if (!_.IsIntVectorType(result_type) &&
-          !_.IsFloatVectorType(result_type)) {
+    case SpvOpImageDrefGather:
+    case SpvOpImageSparseGather:
+    case SpvOpImageSparseDrefGather: {
+      uint32_t actual_result_type = 0;
+      if (spv_result_t error =
+              GetActualResultType(_, *inst, &actual_result_type)) {
+        return error;
+      }
+
+      if (!_.IsIntVectorType(actual_result_type) &&
+          !_.IsFloatVectorType(actual_result_type)) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-               << "Expected Result Type to be int or float vector type: "
+               << "Expected " << GetActualResultTypeStr(opcode)
+               << " to be int or float vector type: "
                << spvOpcodeString(opcode);
       }
 
-      if (_.GetDimension(result_type) != 4) {
+      if (_.GetDimension(actual_result_type) != 4) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-               << "Expected Result Type to have 4 components: "
-               << spvOpcodeString(opcode);
+               << "Expected " << GetActualResultTypeStr(opcode)
+               << " to have 4 components: " << spvOpcodeString(opcode);
       }
 
       const uint32_t image_type = _.GetOperandTypeId(inst, 2);
@@ -978,13 +1088,15 @@ spv_result_t ImagePass(ValidationState_t& _,
       }
 
       if (opcode == SpvOpImageDrefGather ||
+          opcode == SpvOpImageSparseDrefGather ||
           _.GetIdOpcode(info.sampled_type) != SpvOpTypeVoid) {
-        const uint32_t result_component_type = _.GetComponentType(result_type);
+        const uint32_t result_component_type =
+            _.GetComponentType(actual_result_type);
         if (result_component_type != info.sampled_type) {
           return _.diag(SPV_ERROR_INVALID_DATA)
-                 << "Expected Image 'Sampled Type' to be the same as Result "
-                    "Type "
-                 << "components: " << spvOpcodeString(opcode);
+                 << "Expected Image 'Sampled Type' to be the same as "
+                 << GetActualResultTypeStr(opcode)
+                 << " components: " << spvOpcodeString(opcode);
         }
       }
 
@@ -1011,7 +1123,7 @@ spv_result_t ImagePass(ValidationState_t& _,
                << spvOpcodeString(opcode);
       }
 
-      if (opcode == SpvOpImageGather) {
+      if (opcode == SpvOpImageGather || opcode == SpvOpImageSparseGather) {
         const uint32_t component_index_type = _.GetOperandTypeId(inst, 4);
         if (!_.IsIntScalarType(component_index_type) ||
             _.GetBitWidth(component_index_type) != 32) {
@@ -1020,7 +1132,8 @@ spv_result_t ImagePass(ValidationState_t& _,
                  << spvOpcodeString(opcode);
         }
       } else {
-        assert(opcode == SpvOpImageDrefGather);
+        assert(opcode == SpvOpImageDrefGather ||
+               opcode == SpvOpImageSparseDrefGather);
         const uint32_t dref_type = _.GetOperandTypeId(inst, 4);
         if (!_.IsFloatScalarType(dref_type) || _.GetBitWidth(dref_type) != 32) {
           return _.diag(SPV_ERROR_INVALID_DATA)
@@ -1039,21 +1152,28 @@ spv_result_t ImagePass(ValidationState_t& _,
       break;
     }
 
-    case SpvOpImageRead: {
-      if (!_.IsIntScalarOrVectorType(result_type) &&
-          !_.IsFloatScalarOrVectorType(result_type)) {
+    case SpvOpImageRead:
+    case SpvOpImageSparseRead: {
+      uint32_t actual_result_type = 0;
+      if (spv_result_t error =
+              GetActualResultType(_, *inst, &actual_result_type)) {
+        return error;
+      }
+
+      if (!_.IsIntScalarOrVectorType(actual_result_type) &&
+          !_.IsFloatScalarOrVectorType(actual_result_type)) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-               << "Expected Result Type to be int or float scalar or vector "
-                  "type: "
+               << "Expected " << GetActualResultTypeStr(opcode)
+               << " to be int or float scalar or vector type: "
                << spvOpcodeString(opcode);
       }
 
 #if 0
       // TODO(atgoo@github.com) Disabled until the spec is clarified.
-      if (_.GetDimension(result_type) != 4) {
+      if (_.GetDimension(actual_result_type) != 4) {
         return _.diag(SPV_ERROR_INVALID_DATA)
-            << "Expected Result Type to have 4 components: "
-            << spvOpcodeString(opcode);
+               << "Expected " << GetActualResultTypeStr(opcode)
+               << " to have 4 components: " << spvOpcodeString(opcode);
       }
 #endif
 
@@ -1074,16 +1194,17 @@ spv_result_t ImagePass(ValidationState_t& _,
         _.current_function().RegisterExecutionModelLimitation(
             SpvExecutionModelFragment,
             std::string("Dim SubpassData requires Fragment execution model: ") +
-            spvOpcodeString(opcode));
+                spvOpcodeString(opcode));
       }
 
       if (_.GetIdOpcode(info.sampled_type) != SpvOpTypeVoid) {
-        const uint32_t result_component_type = _.GetComponentType(result_type);
+        const uint32_t result_component_type =
+            _.GetComponentType(actual_result_type);
         if (result_component_type != info.sampled_type) {
           return _.diag(SPV_ERROR_INVALID_DATA)
-                 << "Expected Image 'Sampled Type' to be the same as Result "
-                    "Type "
-                 << "components: " << spvOpcodeString(opcode);
+                 << "Expected Image 'Sampled Type' to be the same as "
+                 << GetActualResultTypeStr(opcode)
+                 << " components: " << spvOpcodeString(opcode);
         }
       }
 
@@ -1111,8 +1232,7 @@ spv_result_t ImagePass(ValidationState_t& _,
           !_.HasCapability(SpvCapabilityStorageImageReadWithoutFormat)) {
         return _.diag(SPV_ERROR_INVALID_DATA)
                << "Capability StorageImageReadWithoutFormat is required to "
-                  "read "
-               << "storage image: " << spvOpcodeString(opcode);
+               << "read storage image: " << spvOpcodeString(opcode);
       }
 
       if (inst->num_words <= 5) break;
@@ -1494,6 +1614,32 @@ spv_result_t ImagePass(ValidationState_t& _,
       break;
     }
 
+    case SpvOpImageSparseSampleProjImplicitLod:
+    case SpvOpImageSparseSampleProjExplicitLod:
+    case SpvOpImageSparseSampleProjDrefImplicitLod:
+    case SpvOpImageSparseSampleProjDrefExplicitLod: {
+      return _.diag(SPV_ERROR_INVALID_DATA)
+             << spvOpcodeString(opcode)
+             << ": instruction reserved for future use, "
+             << "use of this instruction is invalid";
+    }
+
+    case SpvOpImageSparseTexelsResident: {
+      if (!_.IsBoolScalarType(result_type)) {
+        return _.diag(SPV_ERROR_INVALID_DATA)
+               << spvOpcodeString(opcode)
+               << ": expected Result Type to be bool scalar type";
+      }
+
+      const uint32_t resident_code_type = _.GetOperandTypeId(inst, 2);
+      if (!_.IsIntScalarType(resident_code_type)) {
+        return _.diag(SPV_ERROR_INVALID_DATA)
+               << spvOpcodeString(opcode)
+               << ": expected Resident Code to be int scalar";
+      }
+      break;
+    }
+
     default:
       break;
   }
index 282621e..24f04ad 100644 (file)
@@ -43,6 +43,7 @@ OpCapability Sampled1D
 OpCapability SampledRect
 OpCapability ImageQuery
 OpCapability Int64
+OpCapability SparseResidency
 )";
 
   ss << capabilities_and_extensions;
@@ -97,6 +98,18 @@ OpCapability Int64
 %u32arr4 = OpTypeArray %u32 %u32_4
 %u32vec3arr4 = OpTypeArray %u32vec3 %u32_4
 
+%struct_u32_f32vec4 = OpTypeStruct %u32 %f32vec4
+%struct_u64_f32vec4 = OpTypeStruct %u64 %f32vec4
+%struct_u32_u32vec4 = OpTypeStruct %u32 %u32vec4
+%struct_u32_f32vec3 = OpTypeStruct %u32 %f32vec3
+%struct_f32_f32vec4 = OpTypeStruct %f32 %f32vec4
+%struct_u32_u32 = OpTypeStruct %u32 %u32
+%struct_f32_f32 = OpTypeStruct %f32 %f32
+%struct_u32 = OpTypeStruct %u32
+%struct_u32_f32_u32 = OpTypeStruct %u32 %f32 %u32
+%struct_u32_f32vec4_u32 = OpTypeStruct %u32 %f32vec4 %u32
+%struct_u32_u32arr4 = OpTypeStruct %u32 %u32arr4
+
 %u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
 %u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
 %u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2
@@ -174,7 +187,7 @@ OpCapability Int64
 
 %type_image_f32_2d_0002 = OpTypeImage %f32 2D 0 0 0 2 Unknown
 %ptr_image_f32_2d_0002 = OpTypePointer UniformConstant %type_image_f32_2d_0002
-%uniform_image_f32_2d_0002 = OpVariable %ptr_image_f32_2d_0001 UniformConstant
+%uniform_image_f32_2d_0002 = OpVariable %ptr_image_f32_2d_0002 UniformConstant
 %type_sampled_image_f32_2d_0002 = OpTypeSampledImage %type_image_f32_2d_0002
 
 %type_image_f32_spd_0002 = OpTypeImage %f32 SubpassData 0 0 0 2 Unknown
@@ -338,94 +351,96 @@ OpEntryPoint Fragment %main "main"
 }
 
 TEST_F(ValidateImage, TypeImageWrongSampledType) {
-  const std::string code = GetShaderHeader() +  R"(
+  const std::string code = GetShaderHeader() + R"(
 %img_type = OpTypeImage %bool 2D 0 0 0 1 Unknown
 )";
 
   CompileSuccessfully(code.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr(
-      "TypeImage: expected Sampled Type to be either void or numerical scalar "
-      "type"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("TypeImage: expected Sampled Type to be either void or "
+                        "numerical scalar "
+                        "type"));
 }
 
 TEST_F(ValidateImage, TypeImageWrongDepth) {
-  const std::string code = GetShaderHeader() +  R"(
+  const std::string code = GetShaderHeader() + R"(
 %img_type = OpTypeImage %f32 2D 3 0 0 1 Unknown
 )";
 
   CompileSuccessfully(code.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr(
-      "TypeImage: invalid Depth 3 (must be 0, 1 or 2)"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("TypeImage: invalid Depth 3 (must be 0, 1 or 2)"));
 }
 
 TEST_F(ValidateImage, TypeImageWrongArrayed) {
-  const std::string code = GetShaderHeader() +  R"(
+  const std::string code = GetShaderHeader() + R"(
 %img_type = OpTypeImage %f32 2D 0 2 0 1 Unknown
 )";
 
   CompileSuccessfully(code.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr(
-      "TypeImage: invalid Arrayed 2 (must be 0 or 1)"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("TypeImage: invalid Arrayed 2 (must be 0 or 1)"));
 }
 
 TEST_F(ValidateImage, TypeImageWrongMS) {
-  const std::string code = GetShaderHeader() +  R"(
+  const std::string code = GetShaderHeader() + R"(
 %img_type = OpTypeImage %f32 2D 0 0 2 1 Unknown
 )";
 
   CompileSuccessfully(code.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr(
-      "TypeImage: invalid MS 2 (must be 0 or 1)"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("TypeImage: invalid MS 2 (must be 0 or 1)"));
 }
 
 TEST_F(ValidateImage, TypeImageWrongSampled) {
-  const std::string code = GetShaderHeader() +  R"(
+  const std::string code = GetShaderHeader() + R"(
 %img_type = OpTypeImage %f32 2D 0 0 0 3 Unknown
 )";
 
   CompileSuccessfully(code.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr(
-      "TypeImage: invalid Sampled 3 (must be 0, 1 or 2)"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("TypeImage: invalid Sampled 3 (must be 0, 1 or 2)"));
 }
 
 TEST_F(ValidateImage, TypeImageWrongSampledForSubpassData) {
   const std::string code = GetShaderHeader("OpCapability InputAttachment\n") +
-      R"(
+                           R"(
 %img_type = OpTypeImage %f32 SubpassData 0 0 0 1 Unknown
 )";
 
   CompileSuccessfully(code.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr(
-      "TypeImage: Dim SubpassData requires Sampled to be 2"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("TypeImage: Dim SubpassData requires Sampled to be 2"));
 }
 
 TEST_F(ValidateImage, TypeImageWrongFormatForSubpassData) {
   const std::string code = GetShaderHeader("OpCapability InputAttachment\n") +
-      R"(
+                           R"(
 %img_type = OpTypeImage %f32 SubpassData 0 0 0 2 Rgba32f
 )";
 
   CompileSuccessfully(code.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr(
-      "TypeImage: Dim SubpassData requires format Unknown"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("TypeImage: Dim SubpassData requires format Unknown"));
 }
 
 TEST_F(ValidateImage, TypeSampledImageNotImage) {
-  const std::string code = GetShaderHeader() +  R"(
+  const std::string code = GetShaderHeader() + R"(
 %simg_type = OpTypeSampledImage %f32
 )";
 
   CompileSuccessfully(code.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr(
-      "TypeSampledImage: expected Image to be of type OpTypeImage"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("TypeSampledImage: expected Image to be of type OpTypeImage"));
 }
 
 TEST_F(ValidateImage, SampledImageSuccess) {
@@ -3369,8 +3384,566 @@ TEST_F(ValidateImage, ReadSubpassDataWrongExecutionModel) {
   const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
   CompileSuccessfully(GenerateShaderCode(body, extra, "Vertex").c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr(
-      "Dim SubpassData requires Fragment execution model: ImageRead"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Dim SubpassData requires Fragment execution model: ImageRead"));
+}
+
+TEST_F(ValidateImage, SparseSampleImplicitLodSuccess) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4 %simg %f32vec2_hh
+%res2 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4 %simg %f32vec2_hh Bias %f32_0_25
+%res4 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4 %simg %f32vec2_hh ConstOffset %s32vec2_01
+%res5 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4 %simg %f32vec2_hh Offset %s32vec2_01
+%res6 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4 %simg %f32vec2_hh MinLod %f32_0_5
+%res7 = OpImageSparseSampleImplicitLod %struct_u64_f32vec4 %simg %f32vec2_hh Bias|Offset|MinLod %f32_0_25 %s32vec2_01 %f32_0_5
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, SparseSampleImplicitLodResultTypeNotStruct) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleImplicitLod %f32 %simg %f32vec2_hh
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseSampleImplicitLod: "
+                        "expected Result Type to be OpTypeStruct"));
+}
+
+TEST_F(ValidateImage, SparseSampleImplicitLodResultTypeNotTwoMembers1) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleImplicitLod %struct_u32 %simg %f32vec2_hh
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseSampleImplicitLod: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseSampleImplicitLodResultTypeNotTwoMembers2) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4_u32 %simg %f32vec2_hh
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseSampleImplicitLod: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseSampleImplicitLodResultTypeFirstMemberNotInt) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleImplicitLod %struct_f32_f32vec4 %simg %f32vec2_hh
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseSampleImplicitLod: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseSampleImplicitLodResultTypeTexelNotVector) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleImplicitLod %struct_u32_u32 %simg %f32vec2_hh
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Result Type's second member to be int or "
+                        "float vector type: ImageSparseSampleImplicitLod"));
+}
+
+TEST_F(ValidateImage, SparseSampleImplicitLodWrongNumComponentsTexel) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleImplicitLod %struct_u32_f32vec3 %simg %f32vec2_hh
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Result Type's second member to have 4 "
+                        "components: ImageSparseSampleImplicitLod"));
+}
+
+TEST_F(ValidateImage, SparseSampleImplicitLodWrongComponentTypeTexel) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleImplicitLod %struct_u32_u32vec4 %simg %f32vec2_hh
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Image 'Sampled Type' to be the same as "
+                        "Result Type's second member components: "
+                        "ImageSparseSampleImplicitLod"));
+}
+
+TEST_F(ValidateImage, SparseSampleDrefImplicitLodSuccess) {
+  const std::string body = R"(
+%img = OpLoad %type_image_u32_2d_0001 %uniform_image_u32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_u32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1
+%res2 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 Bias %f32_0_25
+%res4 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 ConstOffset %s32vec2_01
+%res5 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 Offset %s32vec2_01
+%res6 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 MinLod %f32_0_5
+%res7 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 Bias|Offset|MinLod %f32_0_25 %s32vec2_01 %f32_0_5
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, SparseSampleDrefImplicitLodResultTypeNotStruct) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleDrefImplicitLod %f32 %simg %f32vec2_hh %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseSampleDrefImplicitLod: "
+                        "expected Result Type to be OpTypeStruct"));
+}
+
+TEST_F(ValidateImage, SparseSampleDrefImplicitLodResultTypeNotTwoMembers1) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleDrefImplicitLod %struct_u32 %simg %f32vec2_hh %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("ImageSparseSampleDrefImplicitLod: expected Result Type "
+                "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseSampleDrefImplicitLodResultTypeNotTwoMembers2) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleDrefImplicitLod %struct_u32_f32_u32 %simg %f32vec2_hh %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("ImageSparseSampleDrefImplicitLod: expected Result Type "
+                "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseSampleDrefImplicitLodResultTypeFirstMemberNotInt) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleDrefImplicitLod %struct_f32_f32 %simg %f32vec2_hh %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("ImageSparseSampleDrefImplicitLod: expected Result Type "
+                "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseSampleDrefImplicitLodDifferentSampledType) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Image 'Sampled Type' to be the same as "
+                        "Result Type's second member: "
+                        "ImageSparseSampleDrefImplicitLod"));
+}
+
+TEST_F(ValidateImage, SparseFetchSuccess) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
+%res1 = OpImageSparseFetch %struct_u32_f32vec4 %img %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, SparseFetchResultTypeNotStruct) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
+%res1 = OpImageSparseFetch %f32 %img %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseFetch: "
+                        "expected Result Type to be OpTypeStruct"));
+}
+
+TEST_F(ValidateImage, SparseFetchResultTypeNotTwoMembers1) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
+%res1 = OpImageSparseFetch %struct_u32 %img %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseFetch: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseFetchResultTypeNotTwoMembers2) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
+%res1 = OpImageSparseFetch %struct_u32_f32vec4_u32 %img %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseFetch: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseFetchResultTypeFirstMemberNotInt) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
+%res1 = OpImageSparseFetch %struct_f32_f32vec4 %img %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseFetch: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseFetchResultTypeTexelNotVector) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
+%res1 = OpImageSparseFetch %struct_u32_u32 %img %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Result Type's second member to be int or "
+                        "float vector type: ImageSparseFetch"));
+}
+
+TEST_F(ValidateImage, SparseFetchWrongNumComponentsTexel) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
+%res1 = OpImageSparseFetch %struct_u32_f32vec3 %img %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Result Type's second member to have 4 "
+                        "components: ImageSparseFetch"));
+}
+
+TEST_F(ValidateImage, SparseFetchWrongComponentTypeTexel) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001
+%res1 = OpImageSparseFetch %struct_u32_u32vec4 %img %u32vec2_01
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Image 'Sampled Type' to be the same as "
+                        "Result Type's second member components: "
+                        "ImageSparseFetch"));
+}
+
+TEST_F(ValidateImage, SparseReadSuccess) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
+%res1 = OpImageSparseRead %struct_u32_f32vec4 %img %u32vec2_01
+)";
+
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+  CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, SparseReadResultTypeNotStruct) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
+%res1 = OpImageSparseRead %f32 %img %u32vec2_01
+)";
+
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+  CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseRead: "
+                        "expected Result Type to be OpTypeStruct"));
+}
+
+TEST_F(ValidateImage, SparseReadResultTypeNotTwoMembers1) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
+%res1 = OpImageSparseRead %struct_u32 %img %u32vec2_01
+)";
+
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+  CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseRead: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseReadResultTypeNotTwoMembers2) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
+%res1 = OpImageSparseRead %struct_u32_f32vec4_u32 %img %u32vec2_01
+)";
+
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+  CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseRead: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseReadResultTypeFirstMemberNotInt) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
+%res1 = OpImageSparseRead %struct_f32_f32vec4 %img %u32vec2_01
+)";
+
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+  CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseRead: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseReadResultTypeTexelWrongType) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
+%res1 = OpImageSparseRead %struct_u32_u32arr4 %img %u32vec2_01
+)";
+
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+  CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Result Type's second member to be int or "
+                        "float scalar or vector type: ImageSparseRead"));
+}
+
+TEST_F(ValidateImage, SparseReadWrongComponentTypeTexel) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002
+%res1 = OpImageSparseRead %struct_u32_u32vec4 %img %u32vec2_01
+)";
+
+  const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
+  CompileSuccessfully(GenerateShaderCode(body, extra).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Image 'Sampled Type' to be the same as "
+                        "Result Type's second member components: "
+                        "ImageSparseRead"));
+}
+
+TEST_F(ValidateImage, SparseGatherSuccess) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseGather %struct_u32_f32vec4 %simg %f32vec4_0000 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, SparseGatherResultTypeNotStruct) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseGather %f32 %simg %f32vec2_hh %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseGather: "
+                        "expected Result Type to be OpTypeStruct"));
+}
+
+TEST_F(ValidateImage, SparseGatherResultTypeNotTwoMembers1) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseGather %struct_u32 %simg %f32vec2_hh %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseGather: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseGatherResultTypeNotTwoMembers2) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseGather %struct_u32_f32vec4_u32 %simg %f32vec2_hh %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseGather: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseGatherResultTypeFirstMemberNotInt) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseGather %struct_f32_f32vec4 %simg %f32vec2_hh %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseGather: expected Result Type "
+                        "to be a struct containing an int scalar and a texel"));
+}
+
+TEST_F(ValidateImage, SparseGatherResultTypeTexelNotVector) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseGather %struct_u32_u32 %simg %f32vec2_hh %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Result Type's second member to be int or "
+                        "float vector type: ImageSparseGather"));
+}
+
+TEST_F(ValidateImage, SparseGatherWrongNumComponentsTexel) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseGather %struct_u32_f32vec3 %simg %f32vec2_hh %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Result Type's second member to have 4 "
+                        "components: ImageSparseGather"));
+}
+
+TEST_F(ValidateImage, SparseGatherWrongComponentTypeTexel) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler
+%res1 = OpImageSparseGather %struct_u32_u32vec4 %simg %f32vec2_hh %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Image 'Sampled Type' to be the same as "
+                        "Result Type's second member components: "
+                        "ImageSparseGather"));
+}
+
+TEST_F(ValidateImage, SparseTexelsResidentSuccess) {
+  const std::string body = R"(
+%res1 = OpImageSparseTexelsResident %bool %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, SparseTexelsResidentResultTypeNotBool) {
+  const std::string body = R"(
+%res1 = OpImageSparseTexelsResident %u32 %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImageSparseTexelsResident: "
+                        "expected Result Type to be bool scalar type"));
 }
 
 }  // anonymous namespace