Fix GenerateMipmap when base level or max level are set

According to GLES 3.0.4 section 3.8.10, GenerateMipmap should generate
levels based on the base level, and generate them at most up to the
max level. Levels outside the base/max level range should be unchanged
by GenerateMipmap.

The Texture class is fixed so that the image descs are set only for
the changed mipmap range when GenerateMipmap is called.

The D3D backend is fixed so that mipmap generation is correctly
started from the base level instead of level 0, and making sure that
mipmaps are generated only up to the max level. Generating mipmaps for
array textures is also fixed for cases where the base level depth >=
max(width, height) * 2.

The GL backend is fixed to sync texture state before GenerateMipmap is
called, so that base level and max level are set correctly in the
driver.

The GenerateMipmap entry point is refactored so that it has a separate
validation function and a context function which does the work.
Validation for out-of-range base levels is added.

New tests are added to verify the functionality. One corner case in
the tests fails on NVIDIA GL drivers likely due to a driver bug -
similar rules for GenerateMipmap are found from newer GLES specs and
also OpenGL specs (checked versions 3.3 and 4.4).

BUG=angleproject:596
TEST=angle_end2end_tests

Change-Id: Ifc7b4126281967fc4f6dc4f9452e5b01e39f83d7
Reviewed-on: https://chromium-review.googlesource.com/344514
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index 30c7b54..54a63ac 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -2925,6 +2925,83 @@
     return true;
 }
 
+bool ValidateGenerateMipmap(Context *context, GLenum target)
+{
+    if (!ValidTextureTarget(context, target))
+    {
+        context->handleError(Error(GL_INVALID_ENUM));
+        return false;
+    }
+
+    Texture *texture = context->getTargetTexture(target);
+
+    if (texture == nullptr)
+    {
+        context->handleError(Error(GL_INVALID_OPERATION));
+        return false;
+    }
+
+    const GLuint effectiveBaseLevel = texture->getTextureState().getEffectiveBaseLevel();
+
+    // This error isn't spelled out in the spec in a very explicit way, but we interpret the spec so
+    // that out-of-range base level has a non-color-renderable / non-texture-filterable format.
+    if (effectiveBaseLevel >= gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS)
+    {
+        context->handleError(Error(GL_INVALID_OPERATION));
+        return false;
+    }
+
+    GLenum baseTarget                = (target == GL_TEXTURE_CUBE_MAP) ? GL_TEXTURE_CUBE_MAP_POSITIVE_X : target;
+    GLenum internalFormat            = texture->getInternalFormat(baseTarget, effectiveBaseLevel);
+    const TextureCaps &formatCaps    = context->getTextureCaps().get(internalFormat);
+    const InternalFormat &formatInfo = GetInternalFormatInfo(internalFormat);
+
+    // GenerateMipmap should not generate an INVALID_OPERATION for textures created with
+    // unsized formats or that are color renderable and filterable.  Since we do not track if
+    // the texture was created with sized or unsized format (only sized formats are stored),
+    // it is not possible to make sure the the LUMA formats can generate mipmaps (they should
+    // be able to) because they aren't color renderable.  Simply do a special case for LUMA
+    // textures since they're the only texture format that can be created with unsized formats
+    // that is not color renderable.  New unsized formats are unlikely to be added, since ES2
+    // was the last version to use add them.
+    bool isLUMA = internalFormat == GL_LUMINANCE8_EXT ||
+                  internalFormat == GL_LUMINANCE8_ALPHA8_EXT || internalFormat == GL_ALPHA8_EXT;
+
+    if (formatInfo.depthBits > 0 || formatInfo.stencilBits > 0 || !formatCaps.filterable ||
+        (!formatCaps.renderable && !isLUMA) || formatInfo.compressed)
+    {
+        context->handleError(Error(GL_INVALID_OPERATION));
+        return false;
+    }
+
+    // GL_EXT_sRGB does not support mipmap generation on sRGB textures
+    if (context->getClientVersion() == 2 && formatInfo.colorEncoding == GL_SRGB)
+    {
+        context->handleError(Error(GL_INVALID_OPERATION));
+        return false;
+    }
+
+    // Non-power of 2 ES2 check
+    if (!context->getExtensions().textureNPOT &&
+        (!isPow2(static_cast<int>(texture->getWidth(baseTarget, 0))) ||
+         !isPow2(static_cast<int>(texture->getHeight(baseTarget, 0)))))
+    {
+        ASSERT(context->getClientVersion() <= 2 &&
+               (target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP));
+        context->handleError(Error(GL_INVALID_OPERATION));
+        return false;
+    }
+
+    // Cube completeness check
+    if (target == GL_TEXTURE_CUBE_MAP && !texture->getTextureState().isCubeComplete())
+    {
+        context->handleError(Error(GL_INVALID_OPERATION));
+        return false;
+    }
+
+    return true;
+}
+
 bool ValidateGenBuffers(Context *context, GLint n, GLuint *)
 {
     return ValidateGenOrDelete(context, n);