Add support for multisampled textures.

This Cl adds the AST, WGSL-Reader and SPIRV-Writer support for the
`texture_multisampled_2d` type.

Bug: tint:237
Change-Id: Ib3eb831227b49f32162801f1e1b4e474774c78b3
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/28480
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index dc3b085..67cd7b8 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -331,6 +331,8 @@
     "src/ast/type/i32_type.h",
     "src/ast/type/matrix_type.cc",
     "src/ast/type/matrix_type.h",
+    "src/ast/type/multisampled_texture_type.cc",
+    "src/ast/type/multisampled_texture_type.h",
     "src/ast/type/pointer_type.cc",
     "src/ast/type/pointer_type.h",
     "src/ast/type/sampled_texture_type.cc",
@@ -727,6 +729,7 @@
     "src/ast/type/f32_type_test.cc",
     "src/ast/type/i32_type_test.cc",
     "src/ast/type/matrix_type_test.cc",
+    "src/ast/type/multisampled_texture_type_test.cc",
     "src/ast/type/pointer_type_test.cc",
     "src/ast/type/sampled_texture_type_test.cc",
     "src/ast/type/sampler_type_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index fc5fc11..33cd006 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -154,6 +154,8 @@
   ast/type/i32_type.h
   ast/type/matrix_type.cc
   ast/type/matrix_type.h
+  ast/type/multisampled_texture_type.cc
+  ast/type/multisampled_texture_type.h
   ast/type/pointer_type.cc
   ast/type/pointer_type.h
   ast/type/sampled_texture_type.cc
@@ -337,6 +339,7 @@
   ast/type/f32_type_test.cc
   ast/type/i32_type_test.cc
   ast/type/matrix_type_test.cc
+  ast/type/multisampled_texture_type_test.cc
   ast/type/pointer_type_test.cc
   ast/type/sampled_texture_type_test.cc
   ast/type/sampler_type_test.cc
diff --git a/src/ast/type/multisampled_texture_type.cc b/src/ast/type/multisampled_texture_type.cc
new file mode 100644
index 0000000..7bfc885
--- /dev/null
+++ b/src/ast/type/multisampled_texture_type.cc
@@ -0,0 +1,47 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/ast/type/multisampled_texture_type.h"
+
+#include <cassert>
+#include <sstream>
+
+namespace tint {
+namespace ast {
+namespace type {
+
+MultisampledTextureType::MultisampledTextureType(TextureDimension dim,
+                                                 Type* type)
+    : TextureType(dim), type_(type) {
+  assert(type_);
+}
+
+MultisampledTextureType::MultisampledTextureType(MultisampledTextureType&&) =
+    default;
+
+MultisampledTextureType::~MultisampledTextureType() = default;
+
+bool MultisampledTextureType::IsMultisampled() const {
+  return true;
+}
+
+std::string MultisampledTextureType::type_name() const {
+  std::ostringstream out;
+  out << "__multisampled_texture_" << dim() << type_->type_name();
+  return out.str();
+}
+
+}  // namespace type
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/type/multisampled_texture_type.h b/src/ast/type/multisampled_texture_type.h
new file mode 100644
index 0000000..ae8f38e
--- /dev/null
+++ b/src/ast/type/multisampled_texture_type.h
@@ -0,0 +1,54 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_AST_TYPE_MULTISAMPLED_TEXTURE_TYPE_H_
+#define SRC_AST_TYPE_MULTISAMPLED_TEXTURE_TYPE_H_
+
+#include <string>
+
+#include "src/ast/type/texture_type.h"
+
+namespace tint {
+namespace ast {
+namespace type {
+
+/// A multisampled texture type.
+class MultisampledTextureType : public TextureType {
+ public:
+  /// Constructor
+  /// @param dim the dimensionality of the texture
+  /// @param type the data type of the multisampled texture
+  MultisampledTextureType(TextureDimension dim, Type* type);
+  /// Move constructor
+  MultisampledTextureType(MultisampledTextureType&&);
+  ~MultisampledTextureType() override;
+
+  /// @returns true if the type is a sampled texture type
+  bool IsMultisampled() const override;
+
+  /// @returns the subtype of the sampled texture
+  Type* type() const { return type_; }
+
+  /// @returns the name for this type
+  std::string type_name() const override;
+
+ private:
+  Type* type_ = nullptr;
+};
+
+}  // namespace type
+}  // namespace ast
+}  // namespace tint
+
+#endif  // SRC_AST_TYPE_MULTISAMPLED_TEXTURE_TYPE_H_
diff --git a/src/ast/type/multisampled_texture_type_test.cc b/src/ast/type/multisampled_texture_type_test.cc
new file mode 100644
index 0000000..c50cef9
--- /dev/null
+++ b/src/ast/type/multisampled_texture_type_test.cc
@@ -0,0 +1,74 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/ast/type/multisampled_texture_type.h"
+
+#include "gtest/gtest.h"
+#include "src/ast/type/f32_type.h"
+
+namespace tint {
+namespace ast {
+namespace type {
+namespace {
+
+using MultisampledTextureTypeTest = testing::Test;
+
+TEST_F(MultisampledTextureTypeTest, Is) {
+  F32Type f32;
+  MultisampledTextureType s(TextureDimension::kCube, &f32);
+  EXPECT_FALSE(s.IsAlias());
+  EXPECT_FALSE(s.IsArray());
+  EXPECT_FALSE(s.IsBool());
+  EXPECT_FALSE(s.IsF32());
+  EXPECT_FALSE(s.IsI32());
+  EXPECT_FALSE(s.IsMatrix());
+  EXPECT_FALSE(s.IsPointer());
+  EXPECT_FALSE(s.IsSampler());
+  EXPECT_FALSE(s.IsStruct());
+  EXPECT_TRUE(s.IsTexture());
+  EXPECT_FALSE(s.IsU32());
+  EXPECT_FALSE(s.IsVector());
+}
+
+TEST_F(MultisampledTextureTypeTest, IsTextureType) {
+  F32Type f32;
+  MultisampledTextureType s(TextureDimension::kCube, &f32);
+  EXPECT_FALSE(s.IsDepth());
+  EXPECT_TRUE(s.IsMultisampled());
+  EXPECT_FALSE(s.IsSampled());
+  EXPECT_FALSE(s.IsStorage());
+}
+
+TEST_F(MultisampledTextureTypeTest, Dim) {
+  F32Type f32;
+  MultisampledTextureType s(TextureDimension::k3d, &f32);
+  EXPECT_EQ(s.dim(), TextureDimension::k3d);
+}
+
+TEST_F(MultisampledTextureTypeTest, Type) {
+  F32Type f32;
+  MultisampledTextureType s(TextureDimension::k3d, &f32);
+  EXPECT_EQ(s.type(), &f32);
+}
+
+TEST_F(MultisampledTextureTypeTest, TypeName) {
+  F32Type f32;
+  MultisampledTextureType s(TextureDimension::k3d, &f32);
+  EXPECT_EQ(s.type_name(), "__multisampled_texture_3d__f32");
+}
+
+}  // namespace
+}  // namespace type
+}  // namespace ast
+}  // namespace tint
diff --git a/src/ast/type/texture_type.cc b/src/ast/type/texture_type.cc
index 690a514..ab4e54f 100644
--- a/src/ast/type/texture_type.cc
+++ b/src/ast/type/texture_type.cc
@@ -17,6 +17,7 @@
 #include <cassert>
 
 #include "src/ast/type/depth_texture_type.h"
+#include "src/ast/type/multisampled_texture_type.h"
 #include "src/ast/type/sampled_texture_type.h"
 #include "src/ast/type/storage_texture_type.h"
 
@@ -41,12 +42,6 @@
     case TextureDimension::k2dArray:
       out << "2d_array";
       break;
-    case TextureDimension::k2dMs:
-      out << "2d_ms";
-      break;
-    case TextureDimension::k2dMsArray:
-      out << "2d_ms_array";
-      break;
     case TextureDimension::k3d:
       out << "3d";
       break;
@@ -73,6 +68,9 @@
 bool TextureType::IsDepth() const {
   return false;
 }
+bool TextureType::IsMultisampled() const {
+  return false;
+}
 bool TextureType::IsStorage() const {
   return false;
 }
@@ -85,6 +83,11 @@
   return static_cast<const DepthTextureType*>(this);
 }
 
+const MultisampledTextureType* TextureType::AsMultisampled() const {
+  assert(IsMultisampled());
+  return static_cast<const MultisampledTextureType*>(this);
+}
+
 const SampledTextureType* TextureType::AsSampled() const {
   assert(IsSampled());
   return static_cast<const SampledTextureType*>(this);
@@ -100,6 +103,11 @@
   return static_cast<DepthTextureType*>(this);
 }
 
+MultisampledTextureType* TextureType::AsMultisampled() {
+  assert(IsMultisampled());
+  return static_cast<MultisampledTextureType*>(this);
+}
+
 SampledTextureType* TextureType::AsSampled() {
   assert(IsSampled());
   return static_cast<SampledTextureType*>(this);
diff --git a/src/ast/type/texture_type.h b/src/ast/type/texture_type.h
index e0f44fa..14cac19 100644
--- a/src/ast/type/texture_type.h
+++ b/src/ast/type/texture_type.h
@@ -25,6 +25,7 @@
 namespace type {
 
 class DepthTextureType;
+class MultisampledTextureType;
 class SampledTextureType;
 class StorageTextureType;
 
@@ -40,10 +41,6 @@
   k2d,
   /// 2 dimensional array texture
   k2dArray,
-  /// 2 dimensional multi-sampled texture
-  k2dMs,
-  /// 2 dimensional multi-sampled array texture
-  k2dMsArray,
   /// 3 dimensional texture
   k3d,
   /// cube texture
@@ -71,6 +68,8 @@
 
   /// @returns true if this is a depth texture
   virtual bool IsDepth() const;
+  /// @returns ture if this is a multisampled texture
+  virtual bool IsMultisampled() const;
   /// @returns true if this is a storage texture
   virtual bool IsStorage() const;
   /// @returns true if this is a sampled texture
@@ -78,6 +77,8 @@
 
   /// @returns the texture as a depth texture
   const DepthTextureType* AsDepth() const;
+  /// @returns the texture as a multisampled texture
+  const MultisampledTextureType* AsMultisampled() const;
   /// @returns the texture as a sampled texture
   const SampledTextureType* AsSampled() const;
   /// @returns the texture as a storage texture
@@ -85,6 +86,8 @@
 
   /// @returns the texture as a depth texture
   DepthTextureType* AsDepth();
+  /// @returns the texture as a multisampled texture
+  MultisampledTextureType* AsMultisampled();
   /// @returns the texture as a sampled texture
   SampledTextureType* AsSampled();
   /// @returns the texture as a storage texture
diff --git a/src/reader/wgsl/lexer.cc b/src/reader/wgsl/lexer.cc
index 66a2dc2..37bfeb5 100644
--- a/src/reader/wgsl/lexer.cc
+++ b/src/reader/wgsl/lexer.cc
@@ -657,6 +657,10 @@
     return {Token::Type::kTextureDepthCubeArray, source,
             "texture_depth_cube_array"};
   }
+  if (str == "texture_multisampled_2d") {
+    return {Token::Type::kTextureMultisampled2d, source,
+            "texture_multisampled_2d"};
+  }
   if (str == "texture_ro_1d")
     return {Token::Type::kTextureStorageReadonly1d, source, "texture_ro_1d"};
   if (str == "texture_ro_1d_array")
@@ -681,12 +685,6 @@
     return {Token::Type::kTextureSampled2dArray, source,
             "texture_sampled_2d_array"};
   }
-  if (str == "texture_sampled_2d_ms")
-    return {Token::Type::kTextureSampled2dMs, source, "texture_sampled_2d_ms"};
-  if (str == "texture_sampled_2d_ms_array") {
-    return {Token::Type::kTextureSampled2dMsArray, source,
-            "texture_sampled_2d_ms_array"};
-  }
   if (str == "texture_sampled_3d")
     return {Token::Type::kTextureSampled3d, source, "texture_sampled_3d"};
   if (str == "texture_sampled_cube")
diff --git a/src/reader/wgsl/lexer_test.cc b/src/reader/wgsl/lexer_test.cc
index 4806cb6..d21f3b6 100644
--- a/src/reader/wgsl/lexer_test.cc
+++ b/src/reader/wgsl/lexer_test.cc
@@ -401,7 +401,7 @@
   Lexer l(params.input);
 
   auto t = l.next();
-  EXPECT_TRUE(t.Is(params.type));
+  EXPECT_TRUE(t.Is(params.type)) << params.input;
   EXPECT_EQ(1u, t.line());
   EXPECT_EQ(1u, t.column());
 
@@ -505,6 +505,8 @@
         TokenData{"texture_depth_cube", Token::Type::kTextureDepthCube},
         TokenData{"texture_depth_cube_array",
                   Token::Type::kTextureDepthCubeArray},
+        TokenData{"texture_multisampled_2d",
+                  Token::Type::kTextureMultisampled2d},
         TokenData{"texture_ro_1d", Token::Type::kTextureStorageReadonly1d},
         TokenData{"texture_ro_1d_array",
                   Token::Type::kTextureStorageReadonly1dArray},
@@ -518,9 +520,6 @@
         TokenData{"texture_sampled_2d", Token::Type::kTextureSampled2d},
         TokenData{"texture_sampled_2d_array",
                   Token::Type::kTextureSampled2dArray},
-        TokenData{"texture_sampled_2d_ms", Token::Type::kTextureSampled2dMs},
-        TokenData{"texture_sampled_2d_ms_array",
-                  Token::Type::kTextureSampled2dMsArray},
         TokenData{"texture_sampled_3d", Token::Type::kTextureSampled3d},
         TokenData{"texture_sampled_cube", Token::Type::kTextureSampledCube},
         TokenData{"texture_sampled_cube_array",
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 82af4cc..a85f7c7 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -50,6 +50,7 @@
 #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/multisampled_texture_type.h"
 #include "src/ast/type/pointer_type.h"
 #include "src/ast/type/sampled_texture_type.h"
 #include "src/ast/type/sampler_type.h"
@@ -582,7 +583,7 @@
 //  : sampler_type
 //  | depth_texture_type
 //  | sampled_texture_type LESS_THAN type_decl GREATER_THAN
-//  | TODO: multisampled_texture_type LESS_THAN type_decl GREATER_THAN
+//  | multisampled_texture_type LESS_THAN type_decl GREATER_THAN
 //  | storage_texture_type LESS_THAN image_storage_type GREATER_THAN
 ast::type::Type* ParserImpl::texture_sampler_types() {
   auto* type = sampler_type();
@@ -595,8 +596,8 @@
     return type;
   }
 
-  auto sampled_dim = sampled_texture_type();
-  if (sampled_dim != ast::type::TextureDimension::kNone) {
+  auto dim = sampled_texture_type();
+  if (dim != ast::type::TextureDimension::kNone) {
     auto t = next();
     if (!t.IsLessThan()) {
       set_error(peek(), "missing '<' for sampled texture type");
@@ -618,7 +619,33 @@
     }
 
     return ctx_.type_mgr().Get(
-        std::make_unique<ast::type::SampledTextureType>(sampled_dim, subtype));
+        std::make_unique<ast::type::SampledTextureType>(dim, subtype));
+  }
+
+  dim = multisampled_texture_type();
+  if (dim != ast::type::TextureDimension::kNone) {
+    auto t = next();
+    if (!t.IsLessThan()) {
+      set_error(peek(), "missing '<' for multisampled texture type");
+      return nullptr;
+    }
+
+    auto* subtype = type_decl();
+    if (has_error())
+      return nullptr;
+    if (subtype == nullptr) {
+      set_error(peek(), "invalid subtype for multisampled texture type");
+      return nullptr;
+    }
+
+    t = next();
+    if (!t.IsGreaterThan()) {
+      set_error(peek(), "missing '>' for multisampled texture type");
+      return nullptr;
+    }
+
+    return ctx_.type_mgr().Get(
+        std::make_unique<ast::type::MultisampledTextureType>(dim, subtype));
   }
 
   ast::type::TextureDimension storage_dim;
@@ -675,8 +702,6 @@
 //  | TEXTURE_SAMPLED_1D_ARRAY
 //  | TEXTURE_SAMPLED_2D
 //  | TEXTURE_SAMPLED_2D_ARRAY
-//  | TEXTURE_SAMPLED_2D_MS
-//  | TEXTURE_SAMPLED_2D_MS_ARRAY
 //  | TEXTURE_SAMPLED_3D
 //  | TEXTURE_SAMPLED_CUBE
 //  | TEXTURE_SAMPLED_CUBE_ARRAY
@@ -698,14 +723,6 @@
     next();  // Consume the peek
     return ast::type::TextureDimension::k2dArray;
   }
-  if (t.IsTextureSampled2dMs()) {
-    next();  // Consume the peek
-    return ast::type::TextureDimension::k2dMs;
-  }
-  if (t.IsTextureSampled2dMsArray()) {
-    next();  // Consume the peek
-    return ast::type::TextureDimension::k2dMsArray;
-  }
   if (t.IsTextureSampled3d()) {
     next();  // Consume the peek
     return ast::type::TextureDimension::k3d;
@@ -721,6 +738,17 @@
   return ast::type::TextureDimension::kNone;
 }
 
+// multisampled_texture_type
+//  : TEXTURE_MULTISAMPLED_2D
+ast::type::TextureDimension ParserImpl::multisampled_texture_type() {
+  auto t = peek();
+  if (t.IsTextureMultisampled2d()) {
+    next();  // Consume the peek
+    return ast::type::TextureDimension::k2d;
+  }
+  return ast::type::TextureDimension::kNone;
+}
+
 // storage_texture_type
 //  : TEXTURE_RO_1D
 //  | TEXTURE_RO_1D_ARRAY
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index eb35b3d..6da84df 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -188,6 +188,10 @@
   /// Parses a `sampler_type` grammar element
   /// @returns the parsed Type or nullptr if none matched.
   ast::type::Type* sampler_type();
+  /// Parses a `multisampled_texture_type` grammar element
+  /// @returns returns the multisample texture dimension or kNone if none
+  /// matched.
+  ast::type::TextureDimension multisampled_texture_type();
   /// Parses a `sampled_texture_type` grammar element
   /// @returns returns the sample texture dimension or kNone if none matched.
   ast::type::TextureDimension sampled_texture_type();
diff --git a/src/reader/wgsl/parser_impl_sampled_texture_type_test.cc b/src/reader/wgsl/parser_impl_sampled_texture_type_test.cc
index da9a461..6e838a2 100644
--- a/src/reader/wgsl/parser_impl_sampled_texture_type_test.cc
+++ b/src/reader/wgsl/parser_impl_sampled_texture_type_test.cc
@@ -57,20 +57,6 @@
   EXPECT_FALSE(p->has_error());
 }
 
-TEST_F(ParserImplTest, SampledTextureType_2dMs) {
-  auto* p = parser("texture_sampled_2d_ms");
-  auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k2dMs);
-  EXPECT_FALSE(p->has_error());
-}
-
-TEST_F(ParserImplTest, SampledTextureType_2dMsArray) {
-  auto* p = parser("texture_sampled_2d_ms_array");
-  auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k2dMsArray);
-  EXPECT_FALSE(p->has_error());
-}
-
 TEST_F(ParserImplTest, SampledTextureType_3d) {
   auto* p = parser("texture_sampled_3d");
   auto t = p->sampled_texture_type();
diff --git a/src/reader/wgsl/parser_impl_texture_sampler_types_test.cc b/src/reader/wgsl/parser_impl_texture_sampler_types_test.cc
index 3174004..649920f 100644
--- a/src/reader/wgsl/parser_impl_texture_sampler_types_test.cc
+++ b/src/reader/wgsl/parser_impl_texture_sampler_types_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "gtest/gtest.h"
+#include "src/ast/type/multisampled_texture_type.h"
 #include "src/ast/type/sampled_texture_type.h"
 #include "src/ast/type/sampler_type.h"
 #include "src/reader/wgsl/parser_impl.h"
@@ -33,67 +34,68 @@
 TEST_F(ParserImplTest, TextureSamplerTypes_Sampler) {
   auto* p = parser("sampler");
   auto* t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(t, nullptr);
   ASSERT_TRUE(t->IsSampler());
   ASSERT_FALSE(t->AsSampler()->IsComparison());
-  EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SamplerComparison) {
   auto* p = parser("sampler_comparison");
   auto* t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(t, nullptr);
   ASSERT_TRUE(t->IsSampler());
   ASSERT_TRUE(t->AsSampler()->IsComparison());
-  EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_DepthTexture) {
   auto* p = parser("texture_depth_2d");
   auto* t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(t, nullptr);
   ASSERT_TRUE(t->IsTexture());
   ASSERT_TRUE(t->AsTexture()->IsDepth());
   EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2d);
-  EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_F32) {
   auto* p = parser("texture_sampled_1d<f32>");
   auto* t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(t, nullptr);
   ASSERT_TRUE(t->IsTexture());
   ASSERT_TRUE(t->AsTexture()->IsSampled());
   ASSERT_TRUE(t->AsTexture()->AsSampled()->type()->IsF32());
   EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k1d);
-  EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_I32) {
   auto* p = parser("texture_sampled_2d<i32>");
   auto* t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(t, nullptr);
   ASSERT_TRUE(t->IsTexture());
   ASSERT_TRUE(t->AsTexture()->IsSampled());
   ASSERT_TRUE(t->AsTexture()->AsSampled()->type()->IsI32());
   EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2d);
-  EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_U32) {
   auto* p = parser("texture_sampled_3d<u32>");
   auto* t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(t, nullptr);
   ASSERT_TRUE(t->IsTexture());
   ASSERT_TRUE(t->AsTexture()->IsSampled());
   ASSERT_TRUE(t->AsTexture()->AsSampled()->type()->IsU32());
   EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k3d);
-  EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_Invalid) {
   auto* p = parser("texture_sampled_1d<abc>");
   auto* t = p->texture_sampler_types();
+  ASSERT_TRUE(p->has_error());
   EXPECT_EQ(t, nullptr);
   EXPECT_EQ(p->error(), "1:20: unknown type alias 'abc'");
 }
@@ -101,6 +103,7 @@
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingType) {
   auto* p = parser("texture_sampled_1d<>");
   auto* t = p->texture_sampler_types();
+  ASSERT_TRUE(p->has_error());
   EXPECT_EQ(t, nullptr);
   EXPECT_EQ(p->error(), "1:20: invalid subtype for sampled texture type");
 }
@@ -108,6 +111,7 @@
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingLessThan) {
   auto* p = parser("texture_sampled_1d");
   auto* t = p->texture_sampler_types();
+  ASSERT_TRUE(p->has_error());
   EXPECT_EQ(t, nullptr);
   EXPECT_EQ(p->error(), "1:19: missing '<' for sampled texture type");
 }
@@ -115,13 +119,58 @@
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingGreaterThan) {
   auto* p = parser("texture_sampled_1d<u32");
   auto* t = p->texture_sampler_types();
+  ASSERT_TRUE(p->has_error());
   EXPECT_EQ(t, nullptr);
   EXPECT_EQ(p->error(), "1:23: missing '>' for sampled texture type");
 }
 
+TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_I32) {
+  auto* p = parser("texture_multisampled_2d<i32>");
+  auto* t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(t, nullptr);
+  ASSERT_TRUE(t->IsTexture());
+  ASSERT_TRUE(t->AsTexture()->IsMultisampled());
+  ASSERT_TRUE(t->AsTexture()->AsMultisampled()->type()->IsI32());
+  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2d);
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_Invalid) {
+  auto* p = parser("texture_multisampled_2d<abc>");
+  auto* t = p->texture_sampler_types();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(p->error(), "1:25: unknown type alias 'abc'");
+}
+
+TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_MissingType) {
+  auto* p = parser("texture_multisampled_2d<>");
+  auto* t = p->texture_sampler_types();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(p->error(), "1:25: invalid subtype for multisampled texture type");
+}
+
+TEST_F(ParserImplTest,
+       TextureSamplerTypes_MultisampledTexture_MissingLessThan) {
+  auto* p = parser("texture_multisampled_2d");
+  auto* t = p->texture_sampler_types();
+  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(p->error(), "1:24: missing '<' for multisampled texture type");
+}
+
+TEST_F(ParserImplTest,
+       TextureSamplerTypes_MultisampledTexture_MissingGreaterThan) {
+  auto* p = parser("texture_multisampled_2d<u32");
+  auto* t = p->texture_sampler_types();
+  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(p->error(), "1:28: missing '>' for multisampled texture type");
+}
+
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_Readonly1dR8Unorm) {
   auto* p = parser("texture_ro_1d<r8unorm>");
   auto* t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(t, nullptr);
   ASSERT_TRUE(t->IsTexture());
   ASSERT_TRUE(t->AsTexture()->IsStorage());
@@ -130,12 +179,12 @@
   EXPECT_EQ(t->AsTexture()->AsStorage()->access(),
             ast::type::StorageAccess::kRead);
   EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k1d);
-  EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_Writeonly2dR16Float) {
   auto* p = parser("texture_wo_2d<r16float>");
   auto* t = p->texture_sampler_types();
+  ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(t, nullptr);
   ASSERT_TRUE(t->IsTexture());
   ASSERT_TRUE(t->AsTexture()->IsStorage());
@@ -144,7 +193,6 @@
   EXPECT_EQ(t->AsTexture()->AsStorage()->access(),
             ast::type::StorageAccess::kWrite);
   EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2d);
-  EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidType) {
diff --git a/src/reader/wgsl/token.cc b/src/reader/wgsl/token.cc
index 58d60f4..dd1ad3e 100644
--- a/src/reader/wgsl/token.cc
+++ b/src/reader/wgsl/token.cc
@@ -289,6 +289,8 @@
       return "texture_depth_cube";
     case Token::Type::kTextureDepthCubeArray:
       return "texture_depth_cube_array";
+    case Token::Type::kTextureMultisampled2d:
+      return "texture_multisampled_2d";
     case Token::Type::kTextureStorageReadonly1d:
       return "texture_ro_1d";
     case Token::Type::kTextureStorageReadonly1dArray:
@@ -307,10 +309,6 @@
       return "texture_sampled_2d";
     case Token::Type::kTextureSampled2dArray:
       return "texture_sampled_2d_array";
-    case Token::Type::kTextureSampled2dMs:
-      return "texture_sampled_2d_ms";
-    case Token::Type::kTextureSampled2dMsArray:
-      return "texture_sampled_2d_ms_array";
     case Token::Type::kTextureSampled3d:
       return "texture_sampled_3d";
     case Token::Type::kTextureSampledCube:
diff --git a/src/reader/wgsl/token.h b/src/reader/wgsl/token.h
index 2e9f2da..da18df3 100644
--- a/src/reader/wgsl/token.h
+++ b/src/reader/wgsl/token.h
@@ -300,6 +300,8 @@
     kTextureDepthCube,
     /// A 'texture_depth_cube_array'
     kTextureDepthCubeArray,
+    /// A 'texture_multisampled_2d'
+    kTextureMultisampled2d,
     /// A 'texture_ro_1d'
     kTextureStorageReadonly1d,
     /// A 'texture_ro_2d_array'
@@ -318,10 +320,6 @@
     kTextureSampled2d,
     /// A 'texture_sampled_2d_array'
     kTextureSampled2dArray,
-    /// A 'texture_sampled_2d_ms'
-    kTextureSampled2dMs,
-    /// A 'texture_sampled_2d_ms_array'
-    kTextureSampled2dMsArray,
     /// A 'texture_sampled_3d'
     kTextureSampled3d,
     /// A 'texture_sampled_cube'
@@ -689,6 +687,10 @@
   bool IsTextureDepthCubeArray() const {
     return type_ == Type::kTextureDepthCubeArray;
   }
+  /// @returns true if the token is a 'texture_multisample_2d'
+  bool IsTextureMultisampled2d() const {
+    return type_ == Type::kTextureMultisampled2d;
+  }
   /// @returns true if token is a 'texture_ro_1d'
   bool IsTextureStorageReadonly1d() const {
     return type_ == Type::kTextureStorageReadonly1d;
@@ -721,14 +723,6 @@
   bool IsTextureSampled2dArray() const {
     return type_ == Type::kTextureSampled2dArray;
   }
-  /// @returns true if token is a 'texture_sampled_2d_ms'
-  bool IsTextureSampled2dMs() const {
-    return type_ == Type::kTextureSampled2dMs;
-  }
-  /// @returns true if token is a 'texture_sampled_2d_ms_array'
-  bool IsTextureSampled2dMsArray() const {
-    return type_ == Type::kTextureSampled2dMsArray;
-  }
   /// @returns true if token is a 'texture_sampled_3d'
   bool IsTextureSampled3d() const { return type_ == Type::kTextureSampled3d; }
   /// @returns true if token is a 'texture_sampled_cube'
diff --git a/src/type_determiner_test.cc b/src/type_determiner_test.cc
index 926fbde..512e0b2 100644
--- a/src/type_determiner_test.cc
+++ b/src/type_determiner_test.cc
@@ -1870,8 +1870,7 @@
         return std::make_unique<ast::type::F32Type>();
       }
     } else if (dim == ast::type::TextureDimension::k1dArray ||
-               dim == ast::type::TextureDimension::k2d ||
-               dim == ast::type::TextureDimension::k2dMs) {
+               dim == ast::type::TextureDimension::k2d) {
       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);
@@ -2144,18 +2143,6 @@
                           TextureType::kI32},
         TextureTestParams{ast::type::TextureDimension::k2dArray,
                           TextureType::kU32},
-        TextureTestParams{ast::type::TextureDimension::k2dMs,
-                          TextureType::kF32},
-        TextureTestParams{ast::type::TextureDimension::k2dMs,
-                          TextureType::kI32},
-        TextureTestParams{ast::type::TextureDimension::k2dMs,
-                          TextureType::kU32},
-        TextureTestParams{ast::type::TextureDimension::k2dMsArray,
-                          TextureType::kF32},
-        TextureTestParams{ast::type::TextureDimension::k2dMsArray,
-                          TextureType::kI32},
-        TextureTestParams{ast::type::TextureDimension::k2dMsArray,
-                          TextureType::kU32},
         TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kF32},
         TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kI32},
         TextureTestParams{ast::type::TextureDimension::k3d, TextureType::kU32},
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index bce9333..9754fb2 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -54,6 +54,7 @@
 #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/multisampled_texture_type.h"
 #include "src/ast/type/pointer_type.h"
 #include "src/ast/type/sampled_texture_type.h"
 #include "src/ast/type/storage_texture_type.h"
@@ -2039,7 +2040,6 @@
   auto dim = texture->dim();
   if (dim == ast::type::TextureDimension::k1dArray ||
       dim == ast::type::TextureDimension::k2dArray ||
-      dim == ast::type::TextureDimension::k2dMsArray ||
       dim == ast::type::TextureDimension::kCubeArray) {
     array_literal = 1u;
   }
@@ -2058,8 +2058,7 @@
   }
 
   uint32_t ms_literal = 0u;
-  if (dim == ast::type::TextureDimension::k2dMs ||
-      dim == ast::type::TextureDimension::k2dMsArray) {
+  if (texture->IsMultisampled()) {
     ms_literal = 1u;
   }
 
@@ -2069,7 +2068,7 @@
   }
 
   uint32_t sampled_literal = 2u;
-  if (texture->IsSampled() || texture->IsDepth()) {
+  if (texture->IsMultisampled() || texture->IsSampled() || texture->IsDepth()) {
     sampled_literal = 1u;
   }
 
@@ -2079,6 +2078,8 @@
     type_id = GenerateTypeIfNeeded(&f32);
   } else if (texture->IsSampled()) {
     type_id = GenerateTypeIfNeeded(texture->AsSampled()->type());
+  } else if (texture->IsMultisampled()) {
+    type_id = GenerateTypeIfNeeded(texture->AsMultisampled()->type());
   } else if (texture->IsStorage()) {
     if (texture->AsStorage()->access() == ast::type::StorageAccess::kWrite) {
       ast::type::VoidType void_type;
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index 1adfbb1..e950da4 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -458,8 +458,7 @@
         return std::make_unique<ast::type::F32Type>();
       }
     } else if (dim == ast::type::TextureDimension::k1dArray ||
-               dim == ast::type::TextureDimension::k2d ||
-               dim == ast::type::TextureDimension::k2dMs) {
+               dim == ast::type::TextureDimension::k2d) {
       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);
@@ -523,20 +522,13 @@
 
     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 += "0 ";
     res += std::to_string(sampled_literal) + " ";
 
     if (unknown_format) {
diff --git a/src/writer/spirv/builder_type_test.cc b/src/writer/spirv/builder_type_test.cc
index 48a5652..351b1cf 100644
--- a/src/writer/spirv/builder_type_test.cc
+++ b/src/writer/spirv/builder_type_test.cc
@@ -26,6 +26,7 @@
 #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/multisampled_texture_type.h"
 #include "src/ast/type/pointer_type.h"
 #include "src/ast/type/sampled_texture_type.h"
 #include "src/ast/type/sampler_type.h"
@@ -771,6 +772,53 @@
   return out;
 }
 
+class MultisampledTextureTypeTest : public testing::TestWithParam<TextureType> {
+ public:
+  std::unique_ptr<ast::type::Type> get_type(TextureType param) {
+    if (param == TextureType::kF32) {
+      return std::make_unique<ast::type::F32Type>();
+    }
+    if (param == TextureType::kI32) {
+      return std::make_unique<ast::type::I32Type>();
+    }
+    return std::make_unique<ast::type::U32Type>();
+  }
+
+  std::string get_type_line(TextureType param) {
+    if (param == TextureType::kI32) {
+      return "%2 = OpTypeInt 32 1\n";
+    }
+    if (param == TextureType::kU32) {
+      return "%2 = OpTypeInt 32 0\n";
+    }
+    return "%2 = OpTypeFloat 32\n";
+  }
+};
+
+TEST_P(MultisampledTextureTypeTest, Generate_2d) {
+  auto param = GetParam();
+  auto type = get_type(param);
+
+  ast::type::MultisampledTextureType two_d(ast::type::TextureDimension::k2d,
+                                           type.get());
+
+  ast::Module mod;
+  Builder b(&mod);
+
+  auto id_two_d = b.GenerateTypeIfNeeded(&two_d);
+  ASSERT_FALSE(b.has_error()) << b.error();
+  EXPECT_EQ(1u, id_two_d);
+
+  EXPECT_EQ(DumpInstructions(b.types()),
+            get_type_line(param) + "%1 = OpTypeImage %2 2D 0 0 1 1 Unknown\n");
+}
+
+INSTANTIATE_TEST_SUITE_P(BuilderTest_Type,
+                         MultisampledTextureTypeTest,
+                         testing::Values(TextureType::kU32,
+                                         TextureType::kI32,
+                                         TextureType::kF32));
+
 class SampledTextureTypeTest : public testing::TestWithParam<TextureType> {
  public:
   std::unique_ptr<ast::type::Type> get_type(TextureType param) {
diff --git a/src/writer/wgsl/generator_impl.cc b/src/writer/wgsl/generator_impl.cc
index ec38eb9..c64a1b3 100644
--- a/src/writer/wgsl/generator_impl.cc
+++ b/src/writer/wgsl/generator_impl.cc
@@ -52,6 +52,7 @@
 #include "src/ast/type/array_type.h"
 #include "src/ast/type/depth_texture_type.h"
 #include "src/ast/type/matrix_type.h"
+#include "src/ast/type/multisampled_texture_type.h"
 #include "src/ast/type/pointer_type.h"
 #include "src/ast/type/sampled_texture_type.h"
 #include "src/ast/type/sampler_type.h"
@@ -655,6 +656,8 @@
       out_ << "depth_";
     } else if (texture->IsSampled()) {
       out_ << "sampled_";
+    } else if (texture->IsMultisampled()) {
+      out_ << "multisampled_";
     } else if (texture->IsStorage()) {
       auto* storage = texture->AsStorage();
 
@@ -684,12 +687,6 @@
       case ast::type::TextureDimension::k2dArray:
         out_ << "2d_array";
         break;
-      case ast::type::TextureDimension::k2dMs:
-        out_ << "2d_ms";
-        break;
-      case ast::type::TextureDimension::k2dMsArray:
-        out_ << "2d_ms_array";
-        break;
       case ast::type::TextureDimension::k3d:
         out_ << "3d";
         break;
@@ -712,6 +709,14 @@
         return false;
       }
       out_ << ">";
+    } else if (texture->IsMultisampled()) {
+      auto* sampled = texture->AsMultisampled();
+
+      out_ << "<";
+      if (!EmitType(sampled->type())) {
+        return false;
+      }
+      out_ << ">";
     } else if (texture->IsStorage()) {
       auto* storage = texture->AsStorage();
 
diff --git a/src/writer/wgsl/generator_impl_type_test.cc b/src/writer/wgsl/generator_impl_type_test.cc
index 46e06fc..fe09925 100644
--- a/src/writer/wgsl/generator_impl_type_test.cc
+++ b/src/writer/wgsl/generator_impl_type_test.cc
@@ -24,6 +24,7 @@
 #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/multisampled_texture_type.h"
 #include "src/ast/type/pointer_type.h"
 #include "src/ast/type/sampled_texture_type.h"
 #include "src/ast/type/sampler_type.h"
@@ -276,6 +277,46 @@
         TextureData{ast::type::TextureDimension::kCubeArray,
                     "texture_sampled_cube_array"}));
 
+using WgslGenerator_MultiampledTextureTest =
+    testing::TestWithParam<TextureData>;
+TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_F32) {
+  auto param = GetParam();
+
+  ast::type::F32Type f32;
+  ast::type::MultisampledTextureType t(param.dim, &f32);
+
+  GeneratorImpl g;
+  ASSERT_TRUE(g.EmitType(&t)) << g.error();
+  EXPECT_EQ(g.result(), std::string(param.name) + "<f32>");
+}
+
+TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_I32) {
+  auto param = GetParam();
+
+  ast::type::I32Type i32;
+  ast::type::MultisampledTextureType t(param.dim, &i32);
+
+  GeneratorImpl g;
+  ASSERT_TRUE(g.EmitType(&t)) << g.error();
+  EXPECT_EQ(g.result(), std::string(param.name) + "<i32>");
+}
+
+TEST_P(WgslGenerator_MultiampledTextureTest, EmitType_MultisampledTexture_U32) {
+  auto param = GetParam();
+
+  ast::type::U32Type u32;
+  ast::type::MultisampledTextureType t(param.dim, &u32);
+
+  GeneratorImpl g;
+  ASSERT_TRUE(g.EmitType(&t)) << g.error();
+  EXPECT_EQ(g.result(), std::string(param.name) + "<u32>");
+}
+INSTANTIATE_TEST_SUITE_P(WgslGeneratorImplTest,
+                         WgslGenerator_MultiampledTextureTest,
+                         testing::Values(TextureData{
+                             ast::type::TextureDimension::k2d,
+                             "texture_multisampled_2d"}));
+
 struct StorageTextureData {
   ast::type::ImageFormat fmt;
   ast::type::TextureDimension dim;