validate StorageImageMultisampled capability (#4062)

* validate StorageImageMultisampled capability

The StorageImageMultisampled capability is required when declaring
an image type with with Multisampled==1 and it's a storage image (Sampled == 2)

Fixes #4061

* Allow SubpassData with Multisampled and Sampled==2
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index 299a3ef..17ddfc0 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -829,6 +829,15 @@
     }
   }
 
+  if (info.multisampled && (info.sampled == 2) &&
+      (info.dim != SpvDimSubpassData)) {
+    if (!_.HasCapability(SpvCapabilityStorageImageMultisample)) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << "Capability StorageImageMultisample is required when using "
+                "multisampled storage image";
+    }
+  }
+
   return SPV_SUCCESS;
 }
 
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index 2e84a29..d2e1a55 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -584,6 +584,43 @@
               HasSubstr("Dim SubpassData requires format Unknown"));
 }
 
+TEST_F(ValidateImage, TypeImageMultisampleStorageImage_MissingCapability) {
+  const std::string code = GetShaderHeader("", false) +
+                           R"(
+%img_type = OpTypeImage %f32 2D 0 0 1 2 Rgba32f
+)";
+
+  CompileSuccessfully(code.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()) << code;
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Capability StorageImageMultisample is required when "
+                        "using multisampled storage image"));
+}
+
+TEST_F(ValidateImage, TypeImageMultisampleStorageImage_UsesCapability) {
+  const std::string code =
+      GetShaderHeader("OpCapability StorageImageMultisample\n", false) +
+      R"(
+%img_type = OpTypeImage %f32 2D 0 0 1 2 Rgba32f
+)";
+
+  CompileSuccessfully(code.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << code;
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
+TEST_F(ValidateImage, TypeImageMultisampleSubpassData_OK) {
+  const std::string code =
+      GetShaderHeader("OpCapability InputAttachment\n", false) +
+      R"(
+%img_type = OpTypeImage %f32 SubpassData 0 0 1 2 Unknown
+)";
+
+  CompileSuccessfully(code.c_str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << code;
+  EXPECT_THAT(getDiagnosticString(), Eq(""));
+}
+
 TEST_F(ValidateImage, TypeSampledImageNotImage) {
   const std::string code = GetShaderHeader("", false) + R"(
 %simg_type = OpTypeSampledImage %f32