Fix validation for compressed texture functions.

 * No validation that the format matched the texture level's format for
   SubImage calls.
 * WebGL does not allow the base level to be smaller than the block size.
 * ANGLE used to allow mips of size 3 when this is disallowed.
 * Don't early-exit validation when dimensions are 0, imageSize validation
   happens later.

TEST=conformance/extensions/webgl-compressed-texture-s3tc

BUG=angleproject:1998

Change-Id: I05f5a0b5180344d67b036fdecc17edd2256e85ab
Reviewed-on: https://chromium-review.googlesource.com/480442
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index a4efd03..e2c7af7 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -2136,10 +2136,15 @@
     }
 }
 
+bool ValidCompressedDimension(GLsizei size, GLuint blockSize, bool smallerThanBlockSizeAllowed)
+{
+    return (smallerThanBlockSizeAllowed && (size > 0) && (blockSize % size == 0)) ||
+           (size % blockSize == 0);
+}
+
 bool ValidCompressedImageSize(const ValidationContext *context,
                               GLenum internalFormat,
-                              GLint xoffset,
-                              GLint yoffset,
+                              GLint level,
                               GLsizei width,
                               GLsizei height)
 {
@@ -2149,6 +2154,45 @@
         return false;
     }
 
+    if (width < 0 || height < 0)
+    {
+        return false;
+    }
+
+    if (CompressedTextureFormatRequiresExactSize(internalFormat))
+    {
+        // The ANGLE extensions allow specifying compressed textures with sizes smaller than the
+        // block size for level 0 but WebGL disallows this.
+        bool smallerThanBlockSizeAllowed =
+            level > 0 || !context->getExtensions().webglCompatibility;
+
+        if (!ValidCompressedDimension(width, formatInfo.compressedBlockWidth,
+                                      smallerThanBlockSizeAllowed) ||
+            !ValidCompressedDimension(height, formatInfo.compressedBlockHeight,
+                                      smallerThanBlockSizeAllowed))
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool ValidCompressedSubImageSize(const ValidationContext *context,
+                                 GLenum internalFormat,
+                                 GLint xoffset,
+                                 GLint yoffset,
+                                 GLsizei width,
+                                 GLsizei height,
+                                 size_t textureWidth,
+                                 size_t textureHeight)
+{
+    const gl::InternalFormat &formatInfo = gl::GetSizedInternalFormatInfo(internalFormat);
+    if (!formatInfo.compressed)
+    {
+        return false;
+    }
+
     if (xoffset < 0 || yoffset < 0 || width < 0 || height < 0)
     {
         return false;
@@ -2157,11 +2201,19 @@
     if (CompressedTextureFormatRequiresExactSize(internalFormat))
     {
         if (xoffset % formatInfo.compressedBlockWidth != 0 ||
-            yoffset % formatInfo.compressedBlockHeight != 0 ||
-            (static_cast<GLuint>(width) > formatInfo.compressedBlockWidth &&
-             width % formatInfo.compressedBlockWidth != 0) ||
-            (static_cast<GLuint>(height) > formatInfo.compressedBlockHeight &&
-             height % formatInfo.compressedBlockHeight != 0))
+            yoffset % formatInfo.compressedBlockHeight != 0)
+        {
+            return false;
+        }
+
+        // Allowed to either have data that is a multiple of block size or is smaller than the block
+        // size but fills the entire mip
+        bool fillsEntireMip = xoffset == 0 && yoffset == 0 &&
+                              static_cast<size_t>(width) == textureWidth &&
+                              static_cast<size_t>(height) == textureHeight;
+        bool sizeMultipleOfBlockSize = (width % formatInfo.compressedBlockWidth) == 0 &&
+                                       (height % formatInfo.compressedBlockHeight) == 0;
+        if (!sizeMultipleOfBlockSize && !fillsEntireMip)
         {
             return false;
         }
@@ -3598,14 +3650,7 @@
     const gl::InternalFormat &formatInfo =
         gl::GetInternalFormatInfo(internalformat, GL_UNSIGNED_BYTE);
 
-    if (formatInfo.depthBits > 0)
-    {
-        context->handleError(Error(GL_INVALID_OPERATION));
-        return false;
-    }
-
-    if (formatInfo.compressed &&
-        !ValidCompressedImageSize(context, internalformat, xoffset, yoffset, width, height))
+    if (formatInfo.depthBits > 0 || formatInfo.compressed)
     {
         context->handleError(Error(GL_INVALID_OPERATION));
         return false;