Implement GL_ANGLE_texture_multisample API part

Support GL_ANGLE_texture_multisample extension.
This patch adds enums of multisampled texture and texStorage2DMultisampleANGLE
API.

TEST=angle_end2end_tests.exe --gtest_filter=TextureMultisampleTest*
TEST=angle_end2end_tests.exe --gtest_filter=NegativeTextureMultisampleTest.Negtive*

BUG=angleproject:2275

Change-Id: I2cab997edc33aa2d0be6082381545335423f64e0
Reviewed-on: https://chromium-review.googlesource.com/c/804613
Commit-Queue: Yizhou Jiang <yizhou.jiang@intel.com>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/Caps.cpp b/src/libANGLE/Caps.cpp
index a1121b9..1b495bc 100644
--- a/src/libANGLE/Caps.cpp
+++ b/src/libANGLE/Caps.cpp
@@ -256,7 +256,8 @@
       multiviewMultisample(false),
       blendFuncExtended(false),
       maxDualSourceDrawBuffers(0),
-      memorySize(false)
+      memorySize(false),
+      textureMultisample(false)
 {
 }
 
@@ -890,6 +891,7 @@
         map["GL_OES_texture_storage_multisample_2d_array"] = enableableExtension(&Extensions::textureStorageMultisample2DArray);
         map["GL_ANGLE_multiview_multisample"] = enableableExtension(&Extensions::multiviewMultisample);
         map["GL_EXT_blend_func_extended"] = enableableExtension(&Extensions::blendFuncExtended);
+        map["GL_ANGLE_texture_multisample"] = enableableExtension(&Extensions::textureMultisample);
         // GLES1 extensinos
         map["GL_OES_point_size_array"] = enableableExtension(&Extensions::pointSizeArray);
         map["GL_OES_texture_cube_map"] = enableableExtension(&Extensions::textureCubeMap);
diff --git a/src/libANGLE/Caps.h b/src/libANGLE/Caps.h
index a5c0035..e9f5c33 100644
--- a/src/libANGLE/Caps.h
+++ b/src/libANGLE/Caps.h
@@ -446,6 +446,9 @@
 
     // GL_ANGLE_memory_size
     bool memorySize;
+
+    // GL_ANGLE_texture_multisample
+    bool textureMultisample;
 };
 
 struct ExtensionInfo
diff --git a/src/libANGLE/Compiler.cpp b/src/libANGLE/Compiler.cpp
index 84f0687..fb4a723 100644
--- a/src/libANGLE/Compiler.cpp
+++ b/src/libANGLE/Compiler.cpp
@@ -85,6 +85,7 @@
     mResources.ARB_texture_rectangle           = extensions.textureRectangle;
     mResources.OES_texture_storage_multisample_2d_array =
         extensions.textureStorageMultisample2DArray;
+    mResources.ANGLE_texture_multisample = extensions.textureMultisample;
     // TODO: use shader precision caps to determine if high precision is supported?
     mResources.FragmentPrecisionHigh = 1;
     mResources.EXT_frag_depth        = extensions.fragDepth;
@@ -127,10 +128,10 @@
     mResources.MaxComputeAtomicCounterBuffers =
         caps.maxShaderAtomicCounterBuffers[ShaderType::Compute];
 
-    mResources.MaxVertexAtomicCounters         = caps.maxShaderAtomicCounters[ShaderType::Vertex];
-    mResources.MaxFragmentAtomicCounters       = caps.maxShaderAtomicCounters[ShaderType::Fragment];
-    mResources.MaxCombinedAtomicCounters       = caps.maxCombinedAtomicCounters;
-    mResources.MaxAtomicCounterBindings        = caps.maxAtomicCounterBufferBindings;
+    mResources.MaxVertexAtomicCounters   = caps.maxShaderAtomicCounters[ShaderType::Vertex];
+    mResources.MaxFragmentAtomicCounters = caps.maxShaderAtomicCounters[ShaderType::Fragment];
+    mResources.MaxCombinedAtomicCounters = caps.maxCombinedAtomicCounters;
+    mResources.MaxAtomicCounterBindings  = caps.maxAtomicCounterBufferBindings;
     mResources.MaxVertexAtomicCounterBuffers =
         caps.maxShaderAtomicCounterBuffers[ShaderType::Vertex];
     mResources.MaxFragmentAtomicCounterBuffers =
@@ -150,12 +151,12 @@
     }
 
     // Geometry Shader constants
-    mResources.EXT_geometry_shader              = extensions.geometryShader;
+    mResources.EXT_geometry_shader          = extensions.geometryShader;
     mResources.MaxGeometryUniformComponents = caps.maxShaderUniformComponents[ShaderType::Geometry];
-    mResources.MaxGeometryUniformBlocks         = caps.maxShaderUniformBlocks[ShaderType::Geometry];
-    mResources.MaxGeometryInputComponents       = caps.maxGeometryInputComponents;
-    mResources.MaxGeometryOutputComponents      = caps.maxGeometryOutputComponents;
-    mResources.MaxGeometryOutputVertices        = caps.maxGeometryOutputVertices;
+    mResources.MaxGeometryUniformBlocks     = caps.maxShaderUniformBlocks[ShaderType::Geometry];
+    mResources.MaxGeometryInputComponents   = caps.maxGeometryInputComponents;
+    mResources.MaxGeometryOutputComponents  = caps.maxGeometryOutputComponents;
+    mResources.MaxGeometryOutputVertices    = caps.maxGeometryOutputVertices;
     mResources.MaxGeometryTotalOutputComponents = caps.maxGeometryTotalOutputComponents;
     mResources.MaxGeometryTextureImageUnits = caps.maxShaderTextureImageUnits[ShaderType::Geometry];
 
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index 902d903..f9a37c8 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -417,12 +417,14 @@
         Texture *zeroTexture2DArray = new Texture(mImplementation.get(), 0, TextureType::_2DArray);
         mZeroTextures[TextureType::_2DArray].set(this, zeroTexture2DArray);
     }
-    if (getClientVersion() >= Version(3, 1))
+    if (getClientVersion() >= Version(3, 1) || mSupportedExtensions.textureMultisample)
     {
-        // TODO(http://anglebug.com/2775): These could also be enabled via extension
         Texture *zeroTexture2DMultisample =
             new Texture(mImplementation.get(), 0, TextureType::_2DMultisample);
         mZeroTextures[TextureType::_2DMultisample].set(this, zeroTexture2DMultisample);
+    }
+    if (getClientVersion() >= Version(3, 1))
+    {
         Texture *zeroTexture2DMultisampleArray =
             new Texture(mImplementation.get(), 0, TextureType::_2DMultisampleArray);
         mZeroTextures[TextureType::_2DMultisampleArray].set(this, zeroTexture2DMultisampleArray);
@@ -3183,6 +3185,7 @@
         supportedExtensions.multiview             = false;
         supportedExtensions.maxViews              = 1u;
         supportedExtensions.copyTexture3d         = false;
+        supportedExtensions.textureMultisample    = false;
     }
 
     if (getClientVersion() < ES_3_1)
@@ -7655,6 +7658,20 @@
         }
     }
 
+    if (getExtensions().textureMultisample)
+    {
+        switch (pname)
+        {
+            case GL_MAX_COLOR_TEXTURE_SAMPLES_ANGLE:
+            case GL_MAX_INTEGER_SAMPLES_ANGLE:
+            case GL_MAX_DEPTH_TEXTURE_SAMPLES_ANGLE:
+            case GL_TEXTURE_BINDING_2D_MULTISAMPLE_ANGLE:
+                *type      = GL_INT;
+                *numParams = 1;
+                return true;
+        }
+    }
+
     if (getClientVersion() < Version(3, 1))
     {
         return false;
@@ -8239,7 +8256,7 @@
     mCachedValidBindTextureTypes = {{
         true,                                                    /* _2D */
         isGLES3,                                                 /* _2DArray */
-        isGLES31,                                                /* _2DMultisample */
+        isGLES31 || exts.textureMultisample,                     /* _2DMultisample */
         exts.textureStorageMultisample2DArray,                   /* _2DMultisampleArray */
         isGLES3,                                                 /* _3D */
         exts.eglImageExternal || exts.eglStreamConsumerExternal, /* External */
diff --git a/src/libANGLE/ErrorStrings.h b/src/libANGLE/ErrorStrings.h
index efe054a..99a80d5 100644
--- a/src/libANGLE/ErrorStrings.h
+++ b/src/libANGLE/ErrorStrings.h
@@ -261,6 +261,8 @@
        "GL_TIME_ELAPSED_EXT when the number of views in the "
        "active draw framebuffer is greater than 1.");
 ERRMSG(MultisampleArrayExtensionRequired, "GL_ANGLE_texture_multisample_array not enabled.");
+ERRMSG(MultisampleTextureExtensionOrES31Required,
+       "GL_ANGLE_texture_multisample or GLES 3.1 required.");
 ERRMSG(NameBeginsWithGL, "Attributes that begin with 'gl_' are not allowed.");
 ERRMSG(NegativeAttachments, "Negative number of attachments.");
 ERRMSG(NegativeBufferSize, "Negative buffer size.");
diff --git a/src/libANGLE/State.cpp b/src/libANGLE/State.cpp
index 241aceb..7b332d0 100644
--- a/src/libANGLE/State.cpp
+++ b/src/libANGLE/State.cpp
@@ -314,10 +314,12 @@
         mSamplerTextures[TextureType::_2DArray].resize(caps.maxCombinedTextureImageUnits);
         mSamplerTextures[TextureType::_3D].resize(caps.maxCombinedTextureImageUnits);
     }
+    if (clientVersion >= Version(3, 1) || nativeExtensions.textureMultisample)
+    {
+        mSamplerTextures[TextureType::_2DMultisample].resize(caps.maxCombinedTextureImageUnits);
+    }
     if (clientVersion >= Version(3, 1))
     {
-        // TODO(http://anglebug.com/2775): These could also be enabled via extension
-        mSamplerTextures[TextureType::_2DMultisample].resize(caps.maxCombinedTextureImageUnits);
         mSamplerTextures[TextureType::_2DMultisampleArray].resize(
             caps.maxCombinedTextureImageUnits);
 
@@ -334,7 +336,7 @@
         mSamplerTextures[TextureType::External].resize(caps.maxCombinedTextureImageUnits);
     }
     mCompleteTextureBindings.reserve(caps.maxCombinedTextureImageUnits);
-    mCachedTexturesInitState = InitState::MayNeedInit;
+    mCachedTexturesInitState      = InitState::MayNeedInit;
     mCachedImageTexturesInitState = InitState::MayNeedInit;
     for (uint32_t textureIndex = 0; textureIndex < caps.maxCombinedTextureImageUnits;
          ++textureIndex)
@@ -2838,7 +2840,7 @@
 
     // Initialize to the 'Initialized' state and set to 'MayNeedInit' if any texture is not
     // initialized.
-    mCachedTexturesInitState = InitState::Initialized;
+    mCachedTexturesInitState      = InitState::Initialized;
     mCachedImageTexturesInitState = InitState::Initialized;
 
     const ActiveTextureMask &activeTextures             = mProgram->getActiveSamplersMask();
diff --git a/src/libANGLE/entry_points_enum_autogen.h b/src/libANGLE/entry_points_enum_autogen.h
index 090d279..0c7d971 100644
--- a/src/libANGLE/entry_points_enum_autogen.h
+++ b/src/libANGLE/entry_points_enum_autogen.h
@@ -546,6 +546,7 @@
     TexStorage2D,
     TexStorage2DEXT,
     TexStorage2DMultisample,
+    TexStorage2DMultisampleANGLE,
     TexStorage3D,
     TexStorage3DEXT,
     TexStorage3DMultisampleOES,
diff --git a/src/libANGLE/renderer/gl/TextureGL.cpp b/src/libANGLE/renderer/gl/TextureGL.cpp
index 9b7dd94..1ea4573 100644
--- a/src/libANGLE/renderer/gl/TextureGL.cpp
+++ b/src/libANGLE/renderer/gl/TextureGL.cpp
@@ -1037,9 +1037,21 @@
     if (nativegl::UseTexImage2D(getType()))
     {
         ASSERT(size.depth == 1);
-        functions->texStorage2DMultisample(ToGLenum(type), samples, texStorageFormat.internalFormat,
-                                           size.width, size.height,
-                                           gl::ConvertToGLBoolean(fixedSampleLocations));
+        if (functions->texStorage2DMultisample)
+        {
+            functions->texStorage2DMultisample(
+                ToGLenum(type), samples, texStorageFormat.internalFormat, size.width, size.height,
+                gl::ConvertToGLBoolean(fixedSampleLocations));
+        }
+        else
+        {
+            // texImage2DMultisample is similar to texStorage2DMultisample of es 3.1 core feature,
+            // On macos and some old drivers which doesn't support OpenGL ES 3.1, the function can
+            // be supported by ARB_texture_multisample or OpenGL 3.2 core feature.
+            functions->texImage2DMultisample(
+                ToGLenum(type), samples, texStorageFormat.internalFormat, size.width, size.height,
+                gl::ConvertToGLBoolean(fixedSampleLocations));
+        }
     }
     else if (nativegl::UseTexImage3D(getType()))
     {
diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp
index 42539be..71c105a 100644
--- a/src/libANGLE/renderer/gl/renderergl_utils.cpp
+++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp
@@ -24,9 +24,9 @@
 #include "libANGLE/renderer/gl/WorkaroundsGL.h"
 #include "libANGLE/renderer/gl/formatutilsgl.h"
 
+#include <EGL/eglext.h>
 #include <algorithm>
 #include <sstream>
-#include <EGL/eglext.h>
 
 using angle::CheckedNumeric;
 
@@ -61,7 +61,8 @@
 namespace nativegl_gl
 {
 
-static bool MeetsRequirements(const FunctionsGL *functions, const nativegl::SupportRequirement &requirements)
+static bool MeetsRequirements(const FunctionsGL *functions,
+                              const nativegl::SupportRequirement &requirements)
 {
     bool hasRequiredExtensions = false;
     for (const std::vector<std::string> &exts : requirements.requiredExtensions)
@@ -114,9 +115,11 @@
 
     gl::TextureCaps textureCaps;
 
-    const nativegl::InternalFormat &formatInfo = nativegl::GetInternalFormatInfo(internalFormat, functions->standard);
+    const nativegl::InternalFormat &formatInfo =
+        nativegl::GetInternalFormatInfo(internalFormat, functions->standard);
     textureCaps.texturable = MeetsRequirements(functions, formatInfo.texture);
-    textureCaps.filterable = textureCaps.texturable && MeetsRequirements(functions, formatInfo.filter);
+    textureCaps.filterable =
+        textureCaps.texturable && MeetsRequirements(functions, formatInfo.filter);
     textureCaps.textureAttachment = MeetsRequirements(functions, formatInfo.textureAttachment);
     textureCaps.renderbuffer      = MeetsRequirements(functions, formatInfo.renderbuffer);
 
@@ -240,7 +243,9 @@
     return result[index];
 }
 
-static gl::TypePrecision QueryTypePrecision(const FunctionsGL *functions, GLenum shaderType, GLenum precisionType)
+static gl::TypePrecision QueryTypePrecision(const FunctionsGL *functions,
+                                            GLenum shaderType,
+                                            GLenum precisionType)
 {
     gl::TypePrecision precision;
     functions->getShaderPrecisionFormat(shaderType, precisionType, precision.range.data(),
@@ -288,7 +293,8 @@
     *maxSupportedESVersion = gl::Version(3, 1);
 
     // Table 6.28, implementation dependent values
-    if (functions->isAtLeastGL(gl::Version(4, 3)) || functions->hasGLExtension("GL_ARB_ES3_compatibility") ||
+    if (functions->isAtLeastGL(gl::Version(4, 3)) ||
+        functions->hasGLExtension("GL_ARB_ES3_compatibility") ||
         functions->isAtLeastGLES(gl::Version(3, 0)))
     {
         caps->maxElementIndex = QuerySingleGLInt64(functions, GL_MAX_ELEMENT_INDEX);
@@ -299,8 +305,8 @@
         caps->maxElementIndex = static_cast<GLint64>(std::numeric_limits<unsigned int>::max());
     }
 
-    if (functions->isAtLeastGL(gl::Version(1, 2)) ||
-        functions->isAtLeastGLES(gl::Version(3, 0)) || functions->hasGLESExtension("GL_OES_texture_3D"))
+    if (functions->isAtLeastGL(gl::Version(1, 2)) || functions->isAtLeastGLES(gl::Version(3, 0)) ||
+        functions->hasGLESExtension("GL_OES_texture_3D"))
     {
         caps->max3DTextureSize = QuerySingleGLInt(functions, GL_MAX_3D_TEXTURE_SIZE);
     }
@@ -310,10 +316,12 @@
         LimitVersion(maxSupportedESVersion, gl::Version(2, 0));
     }
 
-    caps->max2DTextureSize = QuerySingleGLInt(functions, GL_MAX_TEXTURE_SIZE); // GL 1.0 / ES 2.0
-    caps->maxCubeMapTextureSize = QuerySingleGLInt(functions, GL_MAX_CUBE_MAP_TEXTURE_SIZE); // GL 1.3 / ES 2.0
+    caps->max2DTextureSize = QuerySingleGLInt(functions, GL_MAX_TEXTURE_SIZE);  // GL 1.0 / ES 2.0
+    caps->maxCubeMapTextureSize =
+        QuerySingleGLInt(functions, GL_MAX_CUBE_MAP_TEXTURE_SIZE);  // GL 1.3 / ES 2.0
 
-    if (functions->isAtLeastGL(gl::Version(3, 0)) || functions->hasGLExtension("GL_EXT_texture_array") ||
+    if (functions->isAtLeastGL(gl::Version(3, 0)) ||
+        functions->hasGLExtension("GL_EXT_texture_array") ||
         functions->isAtLeastGLES(gl::Version(3, 0)))
     {
         caps->maxArrayTextureLayers = QuerySingleGLInt(functions, GL_MAX_ARRAY_TEXTURE_LAYERS);
@@ -324,7 +332,8 @@
         LimitVersion(maxSupportedESVersion, gl::Version(2, 0));
     }
 
-    if (functions->isAtLeastGL(gl::Version(1, 5)) || functions->hasGLExtension("GL_EXT_texture_lod_bias") ||
+    if (functions->isAtLeastGL(gl::Version(1, 5)) ||
+        functions->hasGLExtension("GL_EXT_texture_lod_bias") ||
         functions->isAtLeastGLES(gl::Version(3, 0)))
     {
         caps->maxLODBias = QuerySingleGLFloat(functions, GL_MAX_TEXTURE_LOD_BIAS);
@@ -334,7 +343,8 @@
         LimitVersion(maxSupportedESVersion, gl::Version(2, 0));
     }
 
-    if (functions->isAtLeastGL(gl::Version(3, 0)) || functions->hasGLExtension("GL_EXT_framebuffer_object") ||
+    if (functions->isAtLeastGL(gl::Version(3, 0)) ||
+        functions->hasGLExtension("GL_EXT_framebuffer_object") ||
         functions->isAtLeastGLES(gl::Version(2, 0)))
     {
         caps->maxRenderbufferSize = QuerySingleGLInt(functions, GL_MAX_RENDERBUFFER_SIZE);
@@ -346,8 +356,10 @@
         LimitVersion(maxSupportedESVersion, gl::Version(0, 0));
     }
 
-    if (functions->isAtLeastGL(gl::Version(2, 0)) || functions->hasGLExtension("ARB_draw_buffers") ||
-        functions->isAtLeastGLES(gl::Version(3, 0)) || functions->hasGLESExtension("GL_EXT_draw_buffers"))
+    if (functions->isAtLeastGL(gl::Version(2, 0)) ||
+        functions->hasGLExtension("ARB_draw_buffers") ||
+        functions->isAtLeastGLES(gl::Version(3, 0)) ||
+        functions->hasGLESExtension("GL_EXT_draw_buffers"))
     {
         caps->maxDrawBuffers = QuerySingleGLInt(functions, GL_MAX_DRAW_BUFFERS);
     }
@@ -359,8 +371,10 @@
         LimitVersion(maxSupportedESVersion, gl::Version(2, 0));
     }
 
-    caps->maxViewportWidth = QueryGLIntRange(functions, GL_MAX_VIEWPORT_DIMS, 0); // GL 1.0 / ES 2.0
-    caps->maxViewportHeight = QueryGLIntRange(functions, GL_MAX_VIEWPORT_DIMS, 1); // GL 1.0 / ES 2.0
+    caps->maxViewportWidth =
+        QueryGLIntRange(functions, GL_MAX_VIEWPORT_DIMS, 0);  // GL 1.0 / ES 2.0
+    caps->maxViewportHeight =
+        QueryGLIntRange(functions, GL_MAX_VIEWPORT_DIMS, 1);  // GL 1.0 / ES 2.0
 
     if (functions->standard == STANDARD_GL_DESKTOP &&
         (functions->profile & GL_CONTEXT_CORE_PROFILE_BIT) != 0)
@@ -378,14 +392,15 @@
         caps->maxAliasedPointSize = QueryGLFloatRange(functions, GL_ALIASED_POINT_SIZE_RANGE, 1);
     }
 
-    caps->minAliasedLineWidth = QueryGLFloatRange(functions, GL_ALIASED_LINE_WIDTH_RANGE, 0); // GL 1.2 / ES 2.0
-    caps->maxAliasedLineWidth = QueryGLFloatRange(functions, GL_ALIASED_LINE_WIDTH_RANGE, 1); // GL 1.2 / ES 2.0
+    caps->minAliasedLineWidth =
+        QueryGLFloatRange(functions, GL_ALIASED_LINE_WIDTH_RANGE, 0);  // GL 1.2 / ES 2.0
+    caps->maxAliasedLineWidth =
+        QueryGLFloatRange(functions, GL_ALIASED_LINE_WIDTH_RANGE, 1);  // GL 1.2 / ES 2.0
 
     // Table 6.29, implementation dependent values (cont.)
-    if (functions->isAtLeastGL(gl::Version(1, 2)) ||
-        functions->isAtLeastGLES(gl::Version(3, 0)))
+    if (functions->isAtLeastGL(gl::Version(1, 2)) || functions->isAtLeastGLES(gl::Version(3, 0)))
     {
-        caps->maxElementsIndices = QuerySingleGLInt(functions, GL_MAX_ELEMENTS_INDICES);
+        caps->maxElementsIndices  = QuerySingleGLInt(functions, GL_MAX_ELEMENTS_INDICES);
         caps->maxElementsVertices = QuerySingleGLInt(functions, GL_MAX_ELEMENTS_VERTICES);
     }
     else
@@ -411,22 +426,25 @@
         // Doesn't impact supported version
     }
 
-    // glGetShaderPrecisionFormat is not available until desktop GL version 4.1 or GL_ARB_ES2_compatibility exists
-    if (functions->isAtLeastGL(gl::Version(4, 1)) || functions->hasGLExtension("GL_ARB_ES2_compatibility") ||
+    // glGetShaderPrecisionFormat is not available until desktop GL version 4.1 or
+    // GL_ARB_ES2_compatibility exists
+    if (functions->isAtLeastGL(gl::Version(4, 1)) ||
+        functions->hasGLExtension("GL_ARB_ES2_compatibility") ||
         functions->isAtLeastGLES(gl::Version(2, 0)))
     {
-        caps->vertexHighpFloat = QueryTypePrecision(functions, GL_VERTEX_SHADER, GL_HIGH_FLOAT);
+        caps->vertexHighpFloat   = QueryTypePrecision(functions, GL_VERTEX_SHADER, GL_HIGH_FLOAT);
         caps->vertexMediumpFloat = QueryTypePrecision(functions, GL_VERTEX_SHADER, GL_MEDIUM_FLOAT);
-        caps->vertexLowpFloat = QueryTypePrecision(functions, GL_VERTEX_SHADER, GL_LOW_FLOAT);
+        caps->vertexLowpFloat    = QueryTypePrecision(functions, GL_VERTEX_SHADER, GL_LOW_FLOAT);
         caps->fragmentHighpFloat = QueryTypePrecision(functions, GL_FRAGMENT_SHADER, GL_HIGH_FLOAT);
-        caps->fragmentMediumpFloat = QueryTypePrecision(functions, GL_FRAGMENT_SHADER, GL_MEDIUM_FLOAT);
-        caps->fragmentLowpFloat = QueryTypePrecision(functions, GL_FRAGMENT_SHADER, GL_LOW_FLOAT);
-        caps->vertexHighpInt = QueryTypePrecision(functions, GL_VERTEX_SHADER, GL_HIGH_INT);
-        caps->vertexMediumpInt = QueryTypePrecision(functions, GL_VERTEX_SHADER, GL_MEDIUM_INT);
-        caps->vertexLowpInt = QueryTypePrecision(functions, GL_VERTEX_SHADER, GL_LOW_INT);
-        caps->fragmentHighpInt = QueryTypePrecision(functions, GL_FRAGMENT_SHADER, GL_HIGH_INT);
+        caps->fragmentMediumpFloat =
+            QueryTypePrecision(functions, GL_FRAGMENT_SHADER, GL_MEDIUM_FLOAT);
+        caps->fragmentLowpFloat  = QueryTypePrecision(functions, GL_FRAGMENT_SHADER, GL_LOW_FLOAT);
+        caps->vertexHighpInt     = QueryTypePrecision(functions, GL_VERTEX_SHADER, GL_HIGH_INT);
+        caps->vertexMediumpInt   = QueryTypePrecision(functions, GL_VERTEX_SHADER, GL_MEDIUM_INT);
+        caps->vertexLowpInt      = QueryTypePrecision(functions, GL_VERTEX_SHADER, GL_LOW_INT);
+        caps->fragmentHighpInt   = QueryTypePrecision(functions, GL_FRAGMENT_SHADER, GL_HIGH_INT);
         caps->fragmentMediumpInt = QueryTypePrecision(functions, GL_FRAGMENT_SHADER, GL_MEDIUM_INT);
-        caps->fragmentLowpInt = QueryTypePrecision(functions, GL_FRAGMENT_SHADER, GL_LOW_INT);
+        caps->fragmentLowpInt    = QueryTypePrecision(functions, GL_FRAGMENT_SHADER, GL_LOW_INT);
     }
     else
     {
@@ -456,8 +474,7 @@
     }
 
     // Table 6.31, implementation dependent vertex shader limits
-    if (functions->isAtLeastGL(gl::Version(2, 0)) ||
-        functions->isAtLeastGLES(gl::Version(2, 0)))
+    if (functions->isAtLeastGL(gl::Version(2, 0)) || functions->isAtLeastGLES(gl::Version(2, 0)))
     {
         caps->maxVertexAttributes = QuerySingleGLInt(functions, GL_MAX_VERTEX_ATTRIBS);
         caps->maxShaderUniformComponents[gl::ShaderType::Vertex] =
@@ -471,7 +488,8 @@
         LimitVersion(maxSupportedESVersion, gl::Version(0, 0));
     }
 
-    if (functions->isAtLeastGL(gl::Version(4, 1)) || functions->hasGLExtension("GL_ARB_ES2_compatibility") ||
+    if (functions->isAtLeastGL(gl::Version(4, 1)) ||
+        functions->hasGLExtension("GL_ARB_ES2_compatibility") ||
         functions->isAtLeastGLES(gl::Version(2, 0)))
     {
         caps->maxVertexUniformVectors = QuerySingleGLInt(functions, GL_MAX_VERTEX_UNIFORM_VECTORS);
@@ -488,21 +506,20 @@
             caps->maxShaderUniformComponents[gl::ShaderType::Fragment] / 4;
     }
 
-    if (functions->isAtLeastGL(gl::Version(3, 2)) ||
-        functions->isAtLeastGLES(gl::Version(3, 0)))
+    if (functions->isAtLeastGL(gl::Version(3, 2)) || functions->isAtLeastGLES(gl::Version(3, 0)))
     {
-        caps->maxVertexOutputComponents = QuerySingleGLInt(functions, GL_MAX_VERTEX_OUTPUT_COMPONENTS);
+        caps->maxVertexOutputComponents =
+            QuerySingleGLInt(functions, GL_MAX_VERTEX_OUTPUT_COMPONENTS);
     }
     else
     {
-        // There doesn't seem, to be a desktop extension to add this cap, maybe it could be given a safe limit
-        // instead of limiting the supported ES version.
+        // There doesn't seem, to be a desktop extension to add this cap, maybe it could be given a
+        // safe limit instead of limiting the supported ES version.
         LimitVersion(maxSupportedESVersion, gl::Version(2, 0));
     }
 
     // Table 6.32, implementation dependent fragment shader limits
-    if (functions->isAtLeastGL(gl::Version(2, 0)) ||
-        functions->isAtLeastGLES(gl::Version(2, 0)))
+    if (functions->isAtLeastGL(gl::Version(2, 0)) || functions->isAtLeastGLES(gl::Version(2, 0)))
     {
         caps->maxShaderUniformComponents[gl::ShaderType::Fragment] =
             QuerySingleGLInt(functions, GL_MAX_FRAGMENT_UNIFORM_COMPONENTS);
@@ -515,20 +532,19 @@
         LimitVersion(maxSupportedESVersion, gl::Version(0, 0));
     }
 
-    if (functions->isAtLeastGL(gl::Version(3, 2)) ||
-        functions->isAtLeastGLES(gl::Version(3, 0)))
+    if (functions->isAtLeastGL(gl::Version(3, 2)) || functions->isAtLeastGLES(gl::Version(3, 0)))
     {
-        caps->maxFragmentInputComponents = QuerySingleGLInt(functions, GL_MAX_FRAGMENT_INPUT_COMPONENTS);
+        caps->maxFragmentInputComponents =
+            QuerySingleGLInt(functions, GL_MAX_FRAGMENT_INPUT_COMPONENTS);
     }
     else
     {
-        // There doesn't seem, to be a desktop extension to add this cap, maybe it could be given a safe limit
-        // instead of limiting the supported ES version.
+        // There doesn't seem, to be a desktop extension to add this cap, maybe it could be given a
+        // safe limit instead of limiting the supported ES version.
         LimitVersion(maxSupportedESVersion, gl::Version(2, 0));
     }
 
-    if (functions->isAtLeastGL(gl::Version(3, 0)) ||
-        functions->isAtLeastGLES(gl::Version(3, 0)))
+    if (functions->isAtLeastGL(gl::Version(3, 0)) || functions->isAtLeastGLES(gl::Version(3, 0)))
     {
         caps->minProgramTexelOffset = QuerySingleGLInt(functions, GL_MIN_PROGRAM_TEXEL_OFFSET);
         caps->maxProgramTexelOffset = QuerySingleGLInt(functions, GL_MAX_PROGRAM_TEXEL_OFFSET);
@@ -540,16 +556,19 @@
     }
 
     // Table 6.33, implementation dependent aggregate shader limits
-    if (functions->isAtLeastGL(gl::Version(3, 1)) || functions->hasGLExtension("GL_ARB_uniform_buffer_object") ||
+    if (functions->isAtLeastGL(gl::Version(3, 1)) ||
+        functions->hasGLExtension("GL_ARB_uniform_buffer_object") ||
         functions->isAtLeastGLES(gl::Version(3, 0)))
     {
         caps->maxShaderUniformBlocks[gl::ShaderType::Vertex] =
             QuerySingleGLInt(functions, GL_MAX_VERTEX_UNIFORM_BLOCKS);
         caps->maxShaderUniformBlocks[gl::ShaderType::Fragment] =
             QuerySingleGLInt(functions, GL_MAX_FRAGMENT_UNIFORM_BLOCKS);
-        caps->maxUniformBufferBindings = QuerySingleGLInt(functions, GL_MAX_UNIFORM_BUFFER_BINDINGS);
+        caps->maxUniformBufferBindings =
+            QuerySingleGLInt(functions, GL_MAX_UNIFORM_BUFFER_BINDINGS);
         caps->maxUniformBlockSize = QuerySingleGLInt64(functions, GL_MAX_UNIFORM_BLOCK_SIZE);
-        caps->uniformBufferOffsetAlignment = QuerySingleGLInt(functions, GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
+        caps->uniformBufferOffsetAlignment =
+            QuerySingleGLInt(functions, GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
 
         GLuint maxCombinedUniformBlocks =
             QuerySingleGLInt(functions, GL_MAX_COMBINED_UNIFORM_BLOCKS);
@@ -591,7 +610,8 @@
         LimitVersion(maxSupportedESVersion, gl::Version(0, 0));
     }
 
-    if (functions->isAtLeastGL(gl::Version(4, 1)) || functions->hasGLExtension("GL_ARB_ES2_compatibility") ||
+    if (functions->isAtLeastGL(gl::Version(4, 1)) ||
+        functions->hasGLExtension("GL_ARB_ES2_compatibility") ||
         functions->isAtLeastGLES(gl::Version(2, 0)))
     {
         caps->maxVaryingVectors = QuerySingleGLInt(functions, GL_MAX_VARYING_VECTORS);
@@ -603,7 +623,8 @@
     }
 
     // Determine the max combined texture image units by adding the vertex and fragment limits.  If
-    // the real cap is queried, it would contain the limits for shader types that are not available to ES.
+    // the real cap is queried, it would contain the limits for shader types that are not available
+    // to ES.
     caps->maxCombinedTextureImageUnits = caps->maxShaderTextureImageUnits[gl::ShaderType::Vertex] +
                                          caps->maxShaderTextureImageUnits[gl::ShaderType::Fragment];
 
@@ -612,9 +633,12 @@
         functions->hasGLExtension("GL_ARB_transform_feedback2") ||
         functions->isAtLeastGLES(gl::Version(3, 0)))
     {
-        caps->maxTransformFeedbackInterleavedComponents = QuerySingleGLInt(functions, GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS);
-        caps->maxTransformFeedbackSeparateAttributes = QuerySingleGLInt(functions, GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS);
-        caps->maxTransformFeedbackSeparateComponents = QuerySingleGLInt(functions, GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS);
+        caps->maxTransformFeedbackInterleavedComponents =
+            QuerySingleGLInt(functions, GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS);
+        caps->maxTransformFeedbackSeparateAttributes =
+            QuerySingleGLInt(functions, GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS);
+        caps->maxTransformFeedbackSeparateComponents =
+            QuerySingleGLInt(functions, GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS);
     }
     else
     {
@@ -623,8 +647,10 @@
     }
 
     // Table 6.35, Framebuffer Dependent Values
-    if (functions->isAtLeastGL(gl::Version(3, 0)) || functions->hasGLExtension("GL_EXT_framebuffer_multisample") ||
-        functions->isAtLeastGLES(gl::Version(3, 0)) || functions->hasGLESExtension("GL_EXT_multisampled_render_to_texture"))
+    if (functions->isAtLeastGL(gl::Version(3, 0)) ||
+        functions->hasGLExtension("GL_EXT_framebuffer_multisample") ||
+        functions->isAtLeastGLES(gl::Version(3, 0)) ||
+        functions->hasGLESExtension("GL_EXT_multisampled_render_to_texture"))
     {
         caps->maxSamples = QuerySingleGLInt(functions, GL_MAX_SAMPLES);
     }
@@ -885,30 +911,43 @@
     // Extension support
     extensions->setTextureExtensionSupport(*textureCapsMap);
     extensions->elementIndexUint = functions->standard == STANDARD_GL_DESKTOP ||
-                                   functions->isAtLeastGLES(gl::Version(3, 0)) || functions->hasGLESExtension("GL_OES_element_index_uint");
+                                   functions->isAtLeastGLES(gl::Version(3, 0)) ||
+                                   functions->hasGLESExtension("GL_OES_element_index_uint");
     extensions->getProgramBinary = caps->programBinaryFormats.size() > 0;
-    extensions->readFormatBGRA = functions->isAtLeastGL(gl::Version(1, 2)) || functions->hasGLExtension("GL_EXT_bgra") ||
+    extensions->readFormatBGRA   = functions->isAtLeastGL(gl::Version(1, 2)) ||
+                                 functions->hasGLExtension("GL_EXT_bgra") ||
                                  functions->hasGLESExtension("GL_EXT_read_format_bgra");
     extensions->mapBuffer = functions->isAtLeastGL(gl::Version(1, 5)) ||
                             functions->isAtLeastGLES(gl::Version(3, 0)) ||
                             functions->hasGLESExtension("GL_OES_mapbuffer");
-    extensions->mapBufferRange = functions->isAtLeastGL(gl::Version(3, 0)) || functions->hasGLExtension("GL_ARB_map_buffer_range") ||
-                                 functions->isAtLeastGLES(gl::Version(3, 0)) || functions->hasGLESExtension("GL_EXT_map_buffer_range");
+    extensions->mapBufferRange = functions->isAtLeastGL(gl::Version(3, 0)) ||
+                                 functions->hasGLExtension("GL_ARB_map_buffer_range") ||
+                                 functions->isAtLeastGLES(gl::Version(3, 0)) ||
+                                 functions->hasGLESExtension("GL_EXT_map_buffer_range");
     extensions->textureNPOT = functions->standard == STANDARD_GL_DESKTOP ||
-                              functions->isAtLeastGLES(gl::Version(3, 0)) || functions->hasGLESExtension("GL_OES_texture_npot");
+                              functions->isAtLeastGLES(gl::Version(3, 0)) ||
+                              functions->hasGLESExtension("GL_OES_texture_npot");
     // TODO(jmadill): Investigate emulating EXT_draw_buffers on ES 3.0's core functionality.
     extensions->drawBuffers = functions->isAtLeastGL(gl::Version(2, 0)) ||
                               functions->hasGLExtension("ARB_draw_buffers") ||
                               functions->hasGLESExtension("GL_EXT_draw_buffers");
     extensions->textureStorage = functions->standard == STANDARD_GL_DESKTOP ||
                                  functions->hasGLESExtension("GL_EXT_texture_storage");
-    extensions->textureFilterAnisotropic = functions->hasGLExtension("GL_EXT_texture_filter_anisotropic") || functions->hasGLESExtension("GL_EXT_texture_filter_anisotropic");
-    extensions->occlusionQueryBoolean    = nativegl::SupportsOcclusionQueries(functions);
-    extensions->maxTextureAnisotropy = extensions->textureFilterAnisotropic ? QuerySingleGLFloat(functions, GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 0.0f;
-    extensions->fence = functions->hasGLExtension("GL_NV_fence") || functions->hasGLESExtension("GL_NV_fence");
-    extensions->blendMinMax = functions->isAtLeastGL(gl::Version(1, 5)) || functions->hasGLExtension("GL_EXT_blend_minmax") ||
-                              functions->isAtLeastGLES(gl::Version(3, 0)) || functions->hasGLESExtension("GL_EXT_blend_minmax");
-    extensions->framebufferBlit = (functions->blitFramebuffer != nullptr);
+    extensions->textureFilterAnisotropic =
+        functions->hasGLExtension("GL_EXT_texture_filter_anisotropic") ||
+        functions->hasGLESExtension("GL_EXT_texture_filter_anisotropic");
+    extensions->occlusionQueryBoolean = nativegl::SupportsOcclusionQueries(functions);
+    extensions->maxTextureAnisotropy =
+        extensions->textureFilterAnisotropic
+            ? QuerySingleGLFloat(functions, GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT)
+            : 0.0f;
+    extensions->fence =
+        functions->hasGLExtension("GL_NV_fence") || functions->hasGLESExtension("GL_NV_fence");
+    extensions->blendMinMax = functions->isAtLeastGL(gl::Version(1, 5)) ||
+                              functions->hasGLExtension("GL_EXT_blend_minmax") ||
+                              functions->isAtLeastGLES(gl::Version(3, 0)) ||
+                              functions->hasGLESExtension("GL_EXT_blend_minmax");
+    extensions->framebufferBlit        = (functions->blitFramebuffer != nullptr);
     extensions->framebufferMultisample = caps->maxSamples > 0;
     extensions->standardDerivatives    = functions->isAtLeastGL(gl::Version(2, 0)) ||
                                       functions->hasGLExtension("GL_ARB_fragment_shader") ||
@@ -925,15 +964,17 @@
         // GL_MAX_ARRAY_TEXTURE_LAYERS is guaranteed to be at least 256.
         const int maxLayers = QuerySingleGLInt(functions, GL_MAX_ARRAY_TEXTURE_LAYERS);
         // GL_MAX_VIEWPORTS is guaranteed to be at least 16.
-        const int maxViewports       = QuerySingleGLInt(functions, GL_MAX_VIEWPORTS);
-        extensions->maxViews         = static_cast<GLuint>(
+        const int maxViewports = QuerySingleGLInt(functions, GL_MAX_VIEWPORTS);
+        extensions->maxViews   = static_cast<GLuint>(
             std::min(static_cast<int>(gl::IMPLEMENTATION_ANGLE_MULTIVIEW_MAX_VIEWS),
                      std::min(maxLayers, maxViewports)));
         *multiviewImplementationType = MultiviewImplementationTypeGL::NV_VIEWPORT_ARRAY2;
     }
 
-    extensions->fboRenderMipmap = functions->isAtLeastGL(gl::Version(3, 0)) || functions->hasGLExtension("GL_EXT_framebuffer_object") ||
-                                  functions->isAtLeastGLES(gl::Version(3, 0)) || functions->hasGLESExtension("GL_OES_fbo_render_mipmap");
+    extensions->fboRenderMipmap = functions->isAtLeastGL(gl::Version(3, 0)) ||
+                                  functions->hasGLExtension("GL_EXT_framebuffer_object") ||
+                                  functions->isAtLeastGLES(gl::Version(3, 0)) ||
+                                  functions->hasGLESExtension("GL_OES_fbo_render_mipmap");
     extensions->instancedArrays = functions->isAtLeastGL(gl::Version(3, 1)) ||
                                   (functions->hasGLExtension("GL_ARB_instanced_arrays") &&
                                    (functions->hasGLExtension("GL_ARB_draw_instanced") ||
@@ -976,7 +1017,8 @@
     // the EXT_multisample_compatibility is written against ES3.1 but can apply
     // to earlier versions so therefore we're only checking for the extension string
     // and not the specific GLES version.
-    extensions->multisampleCompatibility = functions->isAtLeastGL(gl::Version(1, 3)) ||
+    extensions->multisampleCompatibility =
+        functions->isAtLeastGL(gl::Version(1, 3)) ||
         functions->hasGLESExtension("GL_EXT_multisample_compatibility");
 
     extensions->framebufferMixedSamples =
@@ -1006,6 +1048,9 @@
     extensions->multiviewMultisample =
         extensions->textureStorageMultisample2DArray && extensions->multiview;
 
+    extensions->textureMultisample = functions->isAtLeastGL(gl::Version(3, 2)) ||
+                                     functions->hasGLExtension("GL_ARB_texture_multisample");
+
     // NV_path_rendering
     // We also need interface query which is available in
     // >= 4.3 core or ARB_interface_query or >= GLES 3.1
@@ -1014,9 +1059,8 @@
         (functions->hasGLExtension("GL_ARB_program_interface_query") ||
          functions->isAtLeastGL(gl::Version(4, 3)));
 
-    const bool canEnableESPathRendering =
-        functions->hasGLESExtension("GL_NV_path_rendering") &&
-        functions->isAtLeastGLES(gl::Version(3, 1));
+    const bool canEnableESPathRendering = functions->hasGLESExtension("GL_NV_path_rendering") &&
+                                          functions->isAtLeastGLES(gl::Version(3, 1));
 
     extensions->pathRendering = canEnableGLPathRendering || canEnableESPathRendering;
 
@@ -1177,7 +1221,7 @@
 #endif
 
 #if defined(ANGLE_PLATFORM_APPLE)
-    workarounds->doWhileGLSLCausesGPUHang = true;
+    workarounds->doWhileGLSLCausesGPUHang                  = true;
     workarounds->useUnusedBlocksWithStandardOrSharedLayout = true;
     workarounds->rewriteFloatUnaryMinusOperator            = IsIntel(vendor);
 #endif
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index 9625028..37c424d 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -510,7 +510,8 @@
             return (context->getClientMajorVersion() >= 3);
 
         case TextureType::_2DMultisample:
-            return (context->getClientVersion() >= Version(3, 1));
+            return (context->getClientVersion() >= Version(3, 1) ||
+                    context->getExtensions().textureMultisample);
         case TextureType::_2DMultisampleArray:
             return context->getExtensions().textureStorageMultisample2DArray;
 
@@ -6412,9 +6413,11 @@
             break;
 
         case GL_TEXTURE_2D_MULTISAMPLE:
-            if (context->getClientVersion() < ES_3_1)
+            if (context->getClientVersion() < ES_3_1 &&
+                !context->getExtensions().textureMultisample)
             {
-                ANGLE_VALIDATION_ERR(context, InvalidEnum(), TextureTargetRequiresES31);
+                ANGLE_VALIDATION_ERR(context, InvalidEnum(),
+                                     MultisampleTextureExtensionOrES31Required);
                 return false;
             }
             break;
@@ -6539,4 +6542,25 @@
     return true;
 }
 
+bool ValidateTexStorage2DMultisampleBase(Context *context,
+                                         TextureType target,
+                                         GLsizei samples,
+                                         GLint internalFormat,
+                                         GLsizei width,
+                                         GLsizei height)
+{
+    if (target != TextureType::_2DMultisample)
+    {
+        ANGLE_VALIDATION_ERR(context, InvalidEnum(), InvalidTarget);
+        return false;
+    }
+
+    if (width < 1 || height < 1)
+    {
+        ANGLE_VALIDATION_ERR(context, InvalidValue(), NegativeSize);
+        return false;
+    }
+
+    return ValidateTexStorageMultisample(context, target, samples, internalFormat, width, height);
+}
 }  // namespace gl
diff --git a/src/libANGLE/validationES.h b/src/libANGLE/validationES.h
index 28b0f6b..b6d9c2b 100644
--- a/src/libANGLE/validationES.h
+++ b/src/libANGLE/validationES.h
@@ -705,6 +705,13 @@
                                    GLsizei width,
                                    GLsizei height);
 
+bool ValidateTexStorage2DMultisampleBase(Context *context,
+                                         TextureType target,
+                                         GLsizei samples,
+                                         GLint internalFormat,
+                                         GLsizei width,
+                                         GLsizei height);
+
 // Utility macro for handling implementation methods inside Validation.
 #define ANGLE_HANDLE_VALIDATION_ERR(X) \
     context->handleError(X);           \
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index 2bcfd24..8db6a85 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -1133,8 +1133,9 @@
             break;
 
         case TextureType::_2DMultisample:
-            ASSERT(context->getClientVersion() < Version(3, 1));
-            ANGLE_VALIDATION_ERR(context, InvalidEnum(), ES31Required);
+            ASSERT(context->getClientVersion() < Version(3, 1) &&
+                   !context->getExtensions().textureMultisample);
+            ANGLE_VALIDATION_ERR(context, InvalidEnum(), MultisampleTextureExtensionOrES31Required);
             break;
 
         case TextureType::_2DMultisampleArray:
@@ -6035,9 +6036,11 @@
 
             case TextureTarget::_2DMultisample:
             {
-                if (context->getClientVersion() < ES_3_1)
+                if (context->getClientVersion() < ES_3_1 &&
+                    !context->getExtensions().textureMultisample)
                 {
-                    ANGLE_VALIDATION_ERR(context, InvalidOperation(), ES31Required);
+                    ANGLE_VALIDATION_ERR(context, InvalidOperation(),
+                                         MultisampleTextureExtensionOrES31Required);
                     return false;
                 }
 
diff --git a/src/libANGLE/validationES3.cpp b/src/libANGLE/validationES3.cpp
index eb12b87..cd2a286 100644
--- a/src/libANGLE/validationES3.cpp
+++ b/src/libANGLE/validationES3.cpp
@@ -4149,4 +4149,23 @@
     return true;
 }
 
+bool ValidateTexStorage2DMultisampleANGLE(Context *context,
+                                          TextureType target,
+                                          GLsizei samples,
+                                          GLint internalFormat,
+                                          GLsizei width,
+                                          GLsizei height,
+                                          GLboolean fixedSampleLocations)
+{
+    if (!context->getExtensions().textureMultisample)
+    {
+        ANGLE_VALIDATION_ERR(context, InvalidOperation(),
+                             MultisampleTextureExtensionOrES31Required);
+        return false;
+    }
+
+    return ValidateTexStorage2DMultisampleBase(context, target, samples, internalFormat, width,
+                                               height);
+}
+
 }  // namespace gl
diff --git a/src/libANGLE/validationES3.h b/src/libANGLE/validationES3.h
index e1a16d8..5f72e24 100644
--- a/src/libANGLE/validationES3.h
+++ b/src/libANGLE/validationES3.h
@@ -633,6 +633,13 @@
                                      const char *name);
 bool ValidateGetFragDataIndexEXT(Context *context, GLuint program, const char *name);
 
+bool ValidateTexStorage2DMultisampleANGLE(Context *context,
+                                          TextureType target,
+                                          GLsizei samples,
+                                          GLint internalFormat,
+                                          GLsizei width,
+                                          GLsizei height,
+                                          GLboolean fixedSampleLocations);
 }  // namespace gl
 
 #endif  // LIBANGLE_VALIDATION_ES3_H_
diff --git a/src/libANGLE/validationES31.cpp b/src/libANGLE/validationES31.cpp
index 96549c0..cd386ad 100644
--- a/src/libANGLE/validationES31.cpp
+++ b/src/libANGLE/validationES31.cpp
@@ -1030,19 +1030,8 @@
         return false;
     }
 
-    if (target != TextureType::_2DMultisample)
-    {
-        context->handleError(InvalidEnum() << "Target must be TEXTURE_2D_MULTISAMPLE.");
-        return false;
-    }
-
-    if (width < 1 || height < 1)
-    {
-        ANGLE_VALIDATION_ERR(context, InvalidValue(), NegativeSize);
-        return false;
-    }
-
-    return ValidateTexStorageMultisample(context, target, samples, internalFormat, width, height);
+    return ValidateTexStorage2DMultisampleBase(context, target, samples, internalFormat, width,
+                                               height);
 }
 
 bool ValidateGetMultisamplefv(Context *context, GLenum pname, GLuint index, GLfloat *val)