Edit

kc3-lang/angle/src/tests/gl_tests/MipmapTest.cpp

Branch :

  • Show log

    Commit

  • Author : Shahbaz Youssefi
    Date : 2020-06-05 00:45:01
    Hash : 57f7b690
    Message : Vulkan: Enable passing tests Bug: angleproject:3950 Change-Id: I091d99951772479ef2fbdb2ac51e5bf52a263da7 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2231983 Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>

  • src/tests/gl_tests/MipmapTest.cpp
  • //
    // Copyright 2015 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    
    #include "test_utils/ANGLETest.h"
    
    #include "test_utils/gl_raii.h"
    
    using namespace angle;
    
    namespace
    {
    
    void TexImageCubeMapFaces(GLint level,
                              GLenum internalformat,
                              GLsizei width,
                              GLenum format,
                              GLenum type,
                              void *pixels)
    {
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, level, internalformat, width, width, 0, format,
                     type, pixels);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, level, internalformat, width, width, 0, format,
                     type, pixels);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, level, internalformat, width, width, 0, format,
                     type, pixels);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, level, internalformat, width, width, 0, format,
                     type, pixels);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, level, internalformat, width, width, 0, format,
                     type, pixels);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, level, internalformat, width, width, 0, format,
                     type, pixels);
    }
    
    class BaseMipmapTest : public ANGLETest
    {
      protected:
        void clearAndDrawQuad(GLuint program, GLsizei viewportWidth, GLsizei viewportHeight)
        {
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);
            glViewport(0, 0, viewportWidth, viewportHeight);
            ASSERT_GL_NO_ERROR();
    
            drawQuad(program, "position", 0.0f);
        }
    };
    
    }  // namespace
    
    class MipmapTest : public BaseMipmapTest
    {
      protected:
        MipmapTest()
            : m2DProgram(0),
              mCubeProgram(0),
              mTexture2D(0),
              mTextureCube(0),
              mLevelZeroBlueInitData(),
              mLevelZeroWhiteInitData(),
              mLevelOneGreenInitData(),
              mLevelTwoRedInitData(),
              mOffscreenFramebuffer(0)
        {
            setWindowWidth(128);
            setWindowHeight(128);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
        }
    
        void setUp2DProgram()
        {
            // Vertex Shader source
            constexpr char kVS[] = R"(attribute vec4 position;
    varying vec2 vTexCoord;
    
    void main()
    {
        gl_Position = position;
        vTexCoord   = (position.xy * 0.5) + 0.5;
    })";
    
            // Fragment Shader source
            constexpr char kFS[] = R"(precision mediump float;
    uniform sampler2D uTexture;
    varying vec2 vTexCoord;
    
    void main()
    {
        gl_FragColor = texture2D(uTexture, vTexCoord);
    })";
    
            m2DProgram = CompileProgram(kVS, kFS);
            ASSERT_NE(0u, m2DProgram);
        }
    
        void setUpCubeProgram()
        {
            // A simple vertex shader for the texture cube
            constexpr char kVS[] = R"(attribute vec4 position;
    varying vec4 vPosition;
    void main()
    {
        gl_Position = position;
        vPosition = position;
    })";
    
            // A very simple fragment shader to sample from the negative-Y face of a texture cube.
            constexpr char kFS[] = R"(precision mediump float;
    uniform samplerCube uTexture;
    varying vec4 vPosition;
    
    void main()
    {
        gl_FragColor = textureCube(uTexture, vec3(vPosition.x, -1, vPosition.y));
    })";
    
            mCubeProgram = CompileProgram(kVS, kFS);
            ASSERT_NE(0u, mCubeProgram);
        }
    
        void testSetUp() override
        {
            setUp2DProgram();
    
            setUpCubeProgram();
    
            mLevelZeroBlueInitData =
                createRGBInitData(getWindowWidth(), getWindowHeight(), 0, 0, 255);  // Blue
            mLevelZeroWhiteInitData =
                createRGBInitData(getWindowWidth(), getWindowHeight(), 255, 255, 255);  // White
            mLevelOneGreenInitData =
                createRGBInitData((getWindowWidth() / 2), (getWindowHeight() / 2), 0, 255, 0);  // Green
            mLevelTwoRedInitData =
                createRGBInitData((getWindowWidth() / 4), (getWindowHeight() / 4), 255, 0, 0);  // Red
    
            glGenFramebuffers(1, &mOffscreenFramebuffer);
            glGenTextures(1, &mTexture2D);
    
            // Initialize the texture2D to be empty, and don't use mips.
            glBindTexture(GL_TEXTURE_2D, mTexture2D);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth(), getWindowHeight(), 0, GL_RGB,
                         GL_UNSIGNED_BYTE, nullptr);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
            ASSERT_EQ(getWindowWidth(), getWindowHeight());
    
            // Create a non-mipped texture cube. Set the negative-Y face to be blue.
            glGenTextures(1, &mTextureCube);
            glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
            TexImageCubeMapFaces(0, GL_RGB, getWindowWidth(), GL_RGB, GL_UNSIGNED_BYTE, nullptr);
            glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, getWindowWidth(), getWindowWidth(),
                         0, GL_RGB, GL_UNSIGNED_BYTE, mLevelZeroBlueInitData.data());
    
            // Complete the texture cube without mipmaps to start with.
            glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override
        {
            glDeleteProgram(m2DProgram);
            glDeleteProgram(mCubeProgram);
            glDeleteFramebuffers(1, &mOffscreenFramebuffer);
            glDeleteTextures(1, &mTexture2D);
            glDeleteTextures(1, &mTextureCube);
        }
    
        std::vector<GLubyte> createRGBInitData(GLint width, GLint height, GLint r, GLint g, GLint b)
        {
            std::vector<GLubyte> data(3 * width * height);
    
            for (int i = 0; i < width * height; i += 1)
            {
                data[3 * i + 0] = static_cast<GLubyte>(r);
                data[3 * i + 1] = static_cast<GLubyte>(g);
                data[3 * i + 2] = static_cast<GLubyte>(b);
            }
    
            return data;
        }
    
        void clearTextureLevel0(GLenum textarget,
                                GLuint texture,
                                GLfloat red,
                                GLfloat green,
                                GLfloat blue,
                                GLfloat alpha)
        {
            glBindFramebuffer(GL_FRAMEBUFFER, mOffscreenFramebuffer);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textarget, texture, 0);
            ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
            glClearColor(red, green, blue, alpha);
            glClear(GL_COLOR_BUFFER_BIT);
            glBindFramebuffer(GL_FRAMEBUFFER, 0);
        }
    
        GLuint m2DProgram;
        GLuint mCubeProgram;
        GLuint mTexture2D;
        GLuint mTextureCube;
    
        std::vector<GLubyte> mLevelZeroBlueInitData;
        std::vector<GLubyte> mLevelZeroWhiteInitData;
        std::vector<GLubyte> mLevelOneGreenInitData;
        std::vector<GLubyte> mLevelTwoRedInitData;
    
      private:
        GLuint mOffscreenFramebuffer;
    };
    
    class MipmapTestES3 : public BaseMipmapTest
    {
      protected:
        MipmapTestES3()
            : mTexture(0),
              mArrayProgram(0),
              mTextureArraySliceUniformLocation(-1),
              m3DProgram(0),
              mTexture3DSliceUniformLocation(-1),
              mTexture3DLODUniformLocation(-1),
              m2DProgram(0)
    
        {
            setWindowWidth(128);
            setWindowHeight(128);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
        }
    
        const char *vertexShaderSource()
        {
            // Don't put "#version ..." on its own line. See [cpp]p1:
            // "If there are sequences of preprocessing tokens within the list of arguments that
            //  would otherwise act as preprocessing directives, the behavior is undefined"
            return
                R"(#version 300 es
    precision highp float;
    in vec4 position;
    out vec2 texcoord;
    
    void main()
    {
        gl_Position = vec4(position.xy, 0.0, 1.0);
        texcoord = (position.xy * 0.5) + 0.5;
    })";
        }
    
        void setUpArrayProgram()
        {
            constexpr char kFS[] = R"(#version 300 es
    precision highp float;
    uniform highp sampler2DArray tex;
    uniform int slice;
    in vec2 texcoord;
    out vec4 out_FragColor;
    
    void main()
    {
        out_FragColor = texture(tex, vec3(texcoord, float(slice)));
    })";
    
            mArrayProgram = CompileProgram(vertexShaderSource(), kFS);
            if (mArrayProgram == 0)
            {
                FAIL() << "shader compilation failed.";
            }
    
            mTextureArraySliceUniformLocation = glGetUniformLocation(mArrayProgram, "slice");
            ASSERT_NE(-1, mTextureArraySliceUniformLocation);
    
            glUseProgram(mArrayProgram);
            glUseProgram(0);
            ASSERT_GL_NO_ERROR();
        }
    
        void setUp3DProgram()
        {
            constexpr char kFS[] = R"(#version 300 es
    precision highp float;
    uniform highp sampler3D tex;
    uniform float slice;
    uniform float lod;
    in vec2 texcoord;
    out vec4 out_FragColor;
    
    void main()
    {
        out_FragColor = textureLod(tex, vec3(texcoord, slice), lod);
    })";
    
            m3DProgram = CompileProgram(vertexShaderSource(), kFS);
            if (m3DProgram == 0)
            {
                FAIL() << "shader compilation failed.";
            }
    
            mTexture3DSliceUniformLocation = glGetUniformLocation(m3DProgram, "slice");
            ASSERT_NE(-1, mTexture3DSliceUniformLocation);
    
            mTexture3DLODUniformLocation = glGetUniformLocation(m3DProgram, "lod");
            ASSERT_NE(-1, mTexture3DLODUniformLocation);
    
            glUseProgram(m3DProgram);
            glUniform1f(mTexture3DLODUniformLocation, 0);
            glUseProgram(0);
            ASSERT_GL_NO_ERROR();
        }
    
        void setUp2DProgram()
        {
            constexpr char kFS[] = R"(#version 300 es
    precision highp float;
    uniform highp sampler2D tex;
    in vec2 texcoord;
    out vec4 out_FragColor;
    
    void main()
    {
        out_FragColor = texture(tex, texcoord);
    })";
    
            m2DProgram = CompileProgram(vertexShaderSource(), kFS);
            ASSERT_NE(0u, m2DProgram);
    
            ASSERT_GL_NO_ERROR();
        }
    
        void setUpCubeProgram()
        {
            // A very simple fragment shader to sample from the negative-Y face of a texture cube.
            constexpr char kFS[] = R"(#version 300 es
    precision mediump float;
    uniform samplerCube uTexture;
    in vec2 texcoord;
    out vec4 out_FragColor;
    
    void main()
    {
        out_FragColor = texture(uTexture, vec3(texcoord.x, -1, texcoord.y));
    })";
    
            mCubeProgram = CompileProgram(vertexShaderSource(), kFS);
            ASSERT_NE(0u, mCubeProgram);
    
            ASSERT_GL_NO_ERROR();
        }
    
        void testSetUp() override
        {
            glGenTextures(1, &mTexture);
            ASSERT_GL_NO_ERROR();
    
            setUpArrayProgram();
            setUp3DProgram();
            setUp2DProgram();
            setUpCubeProgram();
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mTexture);
    
            glDeleteProgram(mArrayProgram);
            glDeleteProgram(m3DProgram);
            glDeleteProgram(m2DProgram);
            glDeleteProgram(mCubeProgram);
        }
    
        GLuint mTexture;
    
        GLuint mArrayProgram;
        GLint mTextureArraySliceUniformLocation;
    
        GLuint m3DProgram;
        GLint mTexture3DSliceUniformLocation;
        GLint mTexture3DLODUniformLocation;
    
        GLuint m2DProgram;
    
        GLuint mCubeProgram;
    };
    
    class MipmapTestES31 : public BaseMipmapTest
    {
      protected:
        MipmapTestES31()
    
        {
            setWindowWidth(128);
            setWindowHeight(128);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
        }
    };
    // This test uses init data for the first three levels of the texture. It passes the level 0 data
    // in, then renders, then level 1, then renders, etc. This ensures that renderers using the zero LOD
    // workaround (e.g. D3D11 FL9_3) correctly pass init data to the mipmapped texture, even if the the
    // zero-LOD texture is currently in use.
    TEST_P(MipmapTest, DISABLED_ThreeLevelsInitData)
    {
        // Pass in level zero init data.
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth(), getWindowHeight(), 0, GL_RGB,
                     GL_UNSIGNED_BYTE, mLevelZeroBlueInitData.data());
        ASSERT_GL_NO_ERROR();
    
        // Disable mips.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        // Draw a full-sized quad, and check it's blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    
        // Draw a half-sized quad, and check it's blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
    
        // Draw a quarter-sized quad, and check it's blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
    
        // Complete the texture by initializing the remaining levels.
        int n = 1;
        while (getWindowWidth() / (1U << n) >= 1)
        {
            glTexImage2D(GL_TEXTURE_2D, n, GL_RGB, getWindowWidth() / (1U << n),
                         getWindowWidth() / (1U << n), 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
            ASSERT_GL_NO_ERROR();
            n += 1;
        }
    
        // Pass in level one init data.
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGB, getWindowWidth() / 2, getWindowHeight() / 2, 0, GL_RGB,
                     GL_UNSIGNED_BYTE, mLevelOneGreenInitData.data());
        ASSERT_GL_NO_ERROR();
    
        // Draw a full-sized quad, and check it's blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    
        // Draw a half-sized quad, and check it's blue. We've not enabled mipmaps yet, so our init data
        // for level one shouldn't be used.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
    
        // Enable mipmaps.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    
        // Draw a half-sized quad, and check it's green.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::green);
    
        // Draw a quarter-sized quad, and check it's black, since we've not passed any init data for
        // level two.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::black);
    
        // Pass in level two init data.
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGB, getWindowWidth() / 4, getWindowHeight() / 4, 0, GL_RGB,
                     GL_UNSIGNED_BYTE, mLevelTwoRedInitData.data());
        ASSERT_GL_NO_ERROR();
    
        // Draw a full-sized quad, and check it's blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    
        // Draw a half-sized quad, and check it's green.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::green);
    
        // Draw a quarter-sized quad, and check it's red.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
    
        // Now disable mipmaps again, and render multiple sized quads. They should all be blue, since
        // level 0 is blue.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
    
        // Now reset level 0 to white, keeping mipmaps disabled. Then, render various sized quads. They
        // should be white.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth(), getWindowHeight(), 0, GL_RGB,
                     GL_UNSIGNED_BYTE, mLevelZeroWhiteInitData.data());
        ASSERT_GL_NO_ERROR();
    
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::white);
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::white);
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::white);
    
        // Then enable mipmaps again. The quads should be white, green, red respectively.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::white);
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::green);
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
    }
    
    // This test generates (and uses) mipmaps on a texture using init data. D3D11 will use a
    // non-renderable TextureStorage for this. The test then disables mips, renders to level zero of the
    // texture, and reenables mips before using the texture again. To do this, D3D11 has to convert the
    // TextureStorage into a renderable one. This test ensures that the conversion works correctly. In
    // particular, on D3D11 Feature Level 9_3 it ensures that both the zero LOD workaround texture AND
    // the 'normal' texture are copied during conversion.
    TEST_P(MipmapTest, GenerateMipmapFromInitDataThenRender)
    {
        // Pass in initial data so the texture is blue.
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth(), getWindowHeight(), 0, GL_RGB,
                     GL_UNSIGNED_BYTE, mLevelZeroBlueInitData.data());
    
        // Then generate the mips.
        glGenerateMipmap(GL_TEXTURE_2D);
        ASSERT_GL_NO_ERROR();
    
        // Enable mipmaps.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    
        // Now draw the texture to various different sized areas.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    
        // Use mip level 1
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
    
        // Use mip level 2
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
    
        ASSERT_GL_NO_ERROR();
    
        // Disable mips. Render a quad using the texture and ensure it's blue.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    
        // Clear level 0 of the texture to red.
        clearTextureLevel0(GL_TEXTURE_2D, mTexture2D, 1.0f, 0.0f, 0.0f, 1.0f);
    
        // Reenable mips, and try rendering different-sized quads.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    
        // Level 0 is now red, so this should render red.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
    
        // Use mip level 1, blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
    
        // Use mip level 2, blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
    }
    
    // This test ensures that mips are correctly generated from a rendered image.
    // In particular, on D3D11 Feature Level 9_3, the clear call will be performed on the zero-level
    // texture, rather than the mipped one. The test ensures that the zero-level texture is correctly
    // copied into the mipped texture before the mipmaps are generated.
    TEST_P(MipmapTest, GenerateMipmapFromRenderedImage)
    {
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        // Clear the texture to blue.
        clearTextureLevel0(GL_TEXTURE_2D, mTexture2D, 0.0f, 0.0f, 1.0f, 1.0f);
    
        // Then generate the mips
        glGenerateMipmap(GL_TEXTURE_2D);
        ASSERT_GL_NO_ERROR();
    
        // Enable mips.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    
        // Now draw the texture to various different sized areas.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    
        // Use mip level 1
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
    
        // Use mip level 2
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
    }
    
    // Test to ensure that rendering to a mipmapped texture works, regardless of whether mipmaps are
    // enabled or not.
    // TODO: This test hits a texture rebind bug in the D3D11 renderer. Fix this.
    TEST_P(MipmapTest, RenderOntoLevelZeroAfterGenerateMipmap)
    {
        // TODO(geofflang): Figure out why this is broken on AMD OpenGL
        ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
    
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        // Clear the texture to blue.
        clearTextureLevel0(GL_TEXTURE_2D, mTexture2D, 0.0f, 0.0f, 1.0f, 1.0f);
    
        // Now, draw the texture to a quad that's the same size as the texture. This draws to the
        // default framebuffer. The quad should be blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    
        // Now go back to the texture, and generate mips on it.
        glGenerateMipmap(GL_TEXTURE_2D);
        ASSERT_GL_NO_ERROR();
    
        // Now try rendering the textured quad again. Note: we've not told GL to use the generated mips.
        // The quad should be blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    
        // Now tell GL to use the generated mips.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        EXPECT_GL_NO_ERROR();
    
        // Now render the textured quad again. It should be still be blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    
        // Now render the textured quad to an area smaller than the texture (i.e. to force
        // minification). This should be blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
    
        // Now clear the texture to green. This just clears the top level. The lower mips should remain
        // blue.
        clearTextureLevel0(GL_TEXTURE_2D, mTexture2D, 0.0f, 1.0f, 0.0f, 1.0f);
    
        // Render a textured quad equal in size to the texture. This should be green, since we just
        // cleared level 0.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::green);
    
        // Render a small textured quad. This forces minification, so should render blue (the color of
        // levels 1+).
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::blue);
    
        // Disable mipmaps again
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        ASSERT_GL_NO_ERROR();
    
        // Render a textured quad equal in size to the texture. This should be green, the color of level
        // 0 in the texture.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::green);
    
        // Render a small textured quad. This would force minification if mips were enabled, but they're
        // not. Therefore, this should be green.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::green);
    }
    
    // This test defines a valid mipchain manually, with an extra level that's unused on the first few
    // draws. Later on, it redefines the whole mipchain but this time, uses the last mip that was
    // already uploaded before. The test expects that mip to be usable.
    TEST_P(MipmapTest, DefineValidExtraLevelAndUseItLater)
    {
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        GLubyte *levels[] = {mLevelZeroBlueInitData.data(), mLevelOneGreenInitData.data(),
                             mLevelTwoRedInitData.data()};
    
        int maxLevel = 1 + static_cast<int>(floor(log2(std::max(getWindowWidth(), getWindowHeight()))));
    
        for (int i = 0; i < maxLevel; i++)
        {
            glTexImage2D(GL_TEXTURE_2D, i, GL_RGB, getWindowWidth() >> i, getWindowHeight() >> i, 0,
                         GL_RGB, GL_UNSIGNED_BYTE, levels[i % 3]);
        }
    
        // Define an extra level that won't be used for now
        std::vector<GLubyte> magentaExtraLevelData =
            createRGBInitData(getWindowWidth() * 2, getWindowHeight() * 2, 255, 0, 255);
        glTexImage2D(GL_TEXTURE_2D, maxLevel, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE,
                     magentaExtraLevelData.data());
    
        ASSERT_GL_NO_ERROR();
    
        // Enable mipmaps.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    
        // Draw a full-sized quad using mip 0, and check it's blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    
        // Draw a full-sized quad using mip 1, and check it's green.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::green);
    
        // Draw a full-sized quad using mip 2, and check it's red.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
    
        // Draw a full-sized quad using the last mip, and check it's green.
        clearAndDrawQuad(m2DProgram, 1, 1);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        // Now redefine everything above level 8 to be a mipcomplete chain again.
        std::vector<GLubyte> levelDoubleSizeYellowInitData =
            createRGBInitData(getWindowWidth() * 2, getWindowHeight() * 2, 255, 255, 0);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth() * 2, getWindowHeight() * 2, 0, GL_RGB,
                     GL_UNSIGNED_BYTE, levelDoubleSizeYellowInitData.data());  // 256
    
        for (int i = 0; i < maxLevel - 1; i++)
        {
            glTexImage2D(GL_TEXTURE_2D, i + 1, GL_RGB, getWindowWidth() >> i, getWindowHeight() >> i, 0,
                         GL_RGB, GL_UNSIGNED_BYTE, levels[i % 3]);
        }
    
        // At this point we have a valid mip chain, the last level being magenta if we draw 1x1 pixel.
        clearAndDrawQuad(m2DProgram, 1, 1);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
    
        // Draw a full-sized quad using mip 0, and check it's yellow.
        clearAndDrawQuad(m2DProgram, getWindowWidth() * 2, getWindowHeight() * 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::yellow);
    
        // Draw a full-sized quad using mip 1, and check it's blue.
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
    
        // Draw a full-sized quad using mip 2, and check it's green.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::green);
    }
    
    // Regression test for a bug that cause mipmaps to only generate using the top left corner as input.
    TEST_P(MipmapTest, MipMapGenerationD3D9Bug)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage") ||
                           !IsGLExtensionEnabled("GL_OES_rgb8_rgba8") ||
                           !IsGLExtensionEnabled("GL_ANGLE_texture_usage"));
    
        const GLColor mip0Color[4] = {
            GLColor::red,
            GLColor::green,
            GLColor::red,
            GLColor::green,
        };
        const GLColor mip1Color = GLColor(127, 127, 0, 255);
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture.get());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexStorage2DEXT(GL_TEXTURE_2D, 2, GL_RGBA8_OES, 2, 2);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color);
        glGenerateMipmap(GL_TEXTURE_2D);
    
        // Only draw to a 1 pixel viewport so the lower mip is used
        clearAndDrawQuad(m2DProgram, 1, 1);
        EXPECT_PIXEL_COLOR_NEAR(0, 0, mip1Color, 1.0);
    }
    
    // This test ensures that the level-zero workaround for TextureCubes (on D3D11 Feature Level 9_3)
    // works as expected. It tests enabling/disabling mipmaps, generating mipmaps, and rendering to
    // level zero.
    TEST_P(MipmapTest, TextureCubeGeneralLevelZero)
    {
        // http://anglebug.com/3145
        ANGLE_SKIP_TEST_IF(IsFuchsia() && IsIntel() && IsVulkan());
    
        // http://anglebug.com/2822
        ANGLE_SKIP_TEST_IF(IsWindows() && IsIntel() && IsVulkan());
    
        glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
    
        // Draw. Since the negative-Y face's is blue, this should be blue.
        clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    
        // Generate mipmaps, and render. This should be blue.
        glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    
        // Draw using a smaller viewport (to force a lower LOD of the texture). This should still be
        // blue.
        clearAndDrawQuad(mCubeProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    
        // Now clear the negative-Y face of the cube to red.
        clearTextureLevel0(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, mTextureCube, 1.0f, 0.0f, 0.0f, 1.0f);
    
        // Draw using a full-size viewport. This should be red.
        clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    
        // Draw using a quarter-size viewport, to force a lower LOD. This should be *BLUE*, since we
        // only cleared level zero of the negative-Y face to red, and left its mipmaps blue.
        clearAndDrawQuad(mCubeProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    
        // Disable mipmaps again, and draw a to a quarter-size viewport.
        // Since this should use level zero of the texture, this should be *RED*.
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        clearAndDrawQuad(mCubeProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    }
    
    // This test ensures that rendering to level-zero of a TextureCube works as expected.
    TEST_P(MipmapTest, TextureCubeRenderToLevelZero)
    {
        // http://anglebug.com/3145
        ANGLE_SKIP_TEST_IF(IsFuchsia() && IsIntel() && IsVulkan());
    
        // http://anglebug.com/2822
        ANGLE_SKIP_TEST_IF(IsWindows() && IsIntel() && IsVulkan());
    
        glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
    
        // Draw. Since the negative-Y face's is blue, this should be blue.
        clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    
        // Now clear the negative-Y face of the cube to red.
        clearTextureLevel0(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, mTextureCube, 1.0f, 0.0f, 0.0f, 1.0f);
    
        // Draw using a full-size viewport. This should be red.
        clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    
        // Draw a to a quarter-size viewport. This should also be red.
        clearAndDrawQuad(mCubeProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    }
    
    // Creates a mipmapped 2D array texture with three layers, and calls ANGLE's GenerateMipmap.
    // Then tests if the mipmaps are rendered correctly for all three layers.
    TEST_P(MipmapTestES3, MipmapsForTextureArray)
    {
        int px = getWindowWidth() / 2;
        int py = getWindowHeight() / 2;
    
        glBindTexture(GL_TEXTURE_2D_ARRAY, mTexture);
    
        glTexStorage3D(GL_TEXTURE_2D_ARRAY, 5, GL_RGBA8, 16, 16, 3);
    
        // Fill the first layer with red
        std::vector<GLColor> pixelsRed(16 * 16, GLColor::red);
        glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        pixelsRed.data());
    
        // Fill the second layer with green
        std::vector<GLColor> pixelsGreen(16 * 16, GLColor::green);
        glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        pixelsGreen.data());
    
        // Fill the third layer with blue
        std::vector<GLColor> pixelsBlue(16 * 16, GLColor::blue);
        glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 2, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        pixelsBlue.data());
    
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
        EXPECT_GL_NO_ERROR();
    
        glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
    
        EXPECT_GL_NO_ERROR();
    
        glUseProgram(mArrayProgram);
    
        EXPECT_GL_NO_ERROR();
    
        // Draw the first slice
        glUniform1i(mTextureArraySliceUniformLocation, 0);
        drawQuad(mArrayProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
    
        // Draw the second slice
        glUniform1i(mTextureArraySliceUniformLocation, 1);
        drawQuad(mArrayProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
    
        // Draw the third slice
        glUniform1i(mTextureArraySliceUniformLocation, 2);
        drawQuad(mArrayProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::blue);
    }
    
    // Create a mipmapped 2D array texture with more layers than width / height, and call
    // GenerateMipmap.
    TEST_P(MipmapTestES3, MipmapForDeepTextureArray)
    {
        int px = getWindowWidth() / 2;
        int py = getWindowHeight() / 2;
    
        glBindTexture(GL_TEXTURE_2D_ARRAY, mTexture);
    
        // Fill the whole texture with red.
        std::vector<GLColor> pixelsRed(2 * 2 * 4, GLColor::red);
        glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 2, 2, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     pixelsRed.data());
    
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
        EXPECT_GL_NO_ERROR();
    
        glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
    
        EXPECT_GL_NO_ERROR();
    
        glUseProgram(mArrayProgram);
    
        EXPECT_GL_NO_ERROR();
    
        // Draw the first slice
        glUniform1i(mTextureArraySliceUniformLocation, 0);
        drawQuad(mArrayProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
    
        // Draw the fourth slice
        glUniform1i(mTextureArraySliceUniformLocation, 3);
        drawQuad(mArrayProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
    }
    
    // Creates a mipmapped 3D texture with two layers, and calls ANGLE's GenerateMipmap.
    // Then tests if the mipmaps are rendered correctly for all two layers.
    TEST_P(MipmapTestES3, MipmapsForTexture3D)
    {
        int px = getWindowWidth() / 2;
        int py = getWindowHeight() / 2;
    
        glBindTexture(GL_TEXTURE_3D, mTexture);
    
        glTexStorage3D(GL_TEXTURE_3D, 5, GL_RGBA8, 16, 16, 2);
    
        // Fill the first layer with red
        std::vector<GLColor> pixelsRed(16 * 16, GLColor::red);
        glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        pixelsRed.data());
    
        // Fill the second layer with green
        std::vector<GLColor> pixelsGreen(16 * 16, GLColor::green);
        glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 1, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        pixelsGreen.data());
    
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
        EXPECT_GL_NO_ERROR();
    
        glGenerateMipmap(GL_TEXTURE_3D);
    
        EXPECT_GL_NO_ERROR();
    
        glUseProgram(m3DProgram);
    
        EXPECT_GL_NO_ERROR();
    
        // Mipmap level 0
        // Draw the first slice
        glUniform1f(mTexture3DLODUniformLocation, 0.);
        glUniform1f(mTexture3DSliceUniformLocation, 0.25f);
        drawQuad(m3DProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
    
        // Draw the second slice
        glUniform1f(mTexture3DSliceUniformLocation, 0.75f);
        drawQuad(m3DProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
    
        // Regenerate mipmap of same color texture
        glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 1, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        pixelsRed.data());
    
        glGenerateMipmap(GL_TEXTURE_3D);
    
        EXPECT_GL_NO_ERROR();
    
        // Mipmap level 1 8*8*1
        glUniform1f(mTexture3DLODUniformLocation, 1.);
        drawQuad(m3DProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
    
        // Mipmap level 2 4*4*1
        glUniform1f(mTexture3DLODUniformLocation, 2.);
        drawQuad(m3DProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
    
        // Mipmap level 3 2*2*1
        glUniform1f(mTexture3DLODUniformLocation, 3.);
        drawQuad(m3DProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
    
        // Mipmap level 4 1*1*1
        glUniform1f(mTexture3DLODUniformLocation, 4.);
        drawQuad(m3DProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
    }
    
    // Create a 2D texture with levels 0-2, call GenerateMipmap with base level 1 so that level 0 stays
    // the same, and then sample levels 0 and 2.
    // GLES 3.0.4 section 3.8.10:
    // "Mipmap generation replaces texel array levels levelbase + 1 through q with arrays derived from
    // the levelbase array, regardless of their previous contents. All other mipmap arrays, including
    // the levelbase array, are left unchanged by this computation."
    TEST_P(MipmapTestES3, GenerateMipmapBaseLevel)
    {
        // Observed incorrect rendering on AMD, sampling level 2 returns black.
        ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL());
    
        glBindTexture(GL_TEXTURE_2D, mTexture);
    
        ASSERT_EQ(getWindowWidth(), getWindowHeight());
    
        // Fill level 0 with blue
        std::vector<GLColor> pixelsBlue(getWindowWidth() * getWindowHeight(), GLColor::blue);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, pixelsBlue.data());
    
        // Fill level 1 with red
        std::vector<GLColor> pixelsRed(getWindowWidth() * getWindowHeight() / 4, GLColor::red);
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, getWindowWidth() / 2, getWindowHeight() / 2, 0,
                     GL_RGBA, GL_UNSIGNED_BYTE, pixelsRed.data());
    
        // Fill level 2 with green
        std::vector<GLColor> pixelsGreen(getWindowWidth() * getWindowHeight() / 16, GLColor::green);
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, getWindowWidth() / 4, getWindowHeight() / 4, 0,
                     GL_RGBA, GL_UNSIGNED_BYTE, pixelsGreen.data());
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        // The blue level 0 should be untouched by this since base level is 1.
        glGenerateMipmap(GL_TEXTURE_2D);
    
        EXPECT_GL_NO_ERROR();
    
        // Draw using level 2. It should be set to red by GenerateMipmap.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
    
        // Draw using level 0. It should still be blue.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    }
    
    // Create a cube map with levels 0-2, call GenerateMipmap with base level 1 so that level 0 stays
    // the same, and then sample levels 0 and 2.
    // GLES 3.0.4 section 3.8.10:
    // "Mipmap generation replaces texel array levels levelbase + 1 through q with arrays derived from
    // the levelbase array, regardless of their previous contents. All other mipmap arrays, including
    // the levelbase array, are left unchanged by this computation."
    TEST_P(MipmapTestES3, GenerateMipmapCubeBaseLevel)
    {
        // Observed incorrect rendering on AMD, sampling level 2 returns black.
        ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL());
    
        ASSERT_EQ(getWindowWidth(), getWindowHeight());
    
        glBindTexture(GL_TEXTURE_CUBE_MAP, mTexture);
        std::vector<GLColor> pixelsBlue(getWindowWidth() * getWindowWidth(), GLColor::blue);
        TexImageCubeMapFaces(0, GL_RGBA8, getWindowWidth(), GL_RGBA, GL_UNSIGNED_BYTE,
                             pixelsBlue.data());
    
        // Fill level 1 with red
        std::vector<GLColor> pixelsRed(getWindowWidth() * getWindowWidth() / 4, GLColor::red);
        TexImageCubeMapFaces(1, GL_RGBA8, getWindowWidth() / 2, GL_RGBA, GL_UNSIGNED_BYTE,
                             pixelsRed.data());
    
        // Fill level 2 with green
        std::vector<GLColor> pixelsGreen(getWindowWidth() * getWindowWidth() / 16, GLColor::green);
        TexImageCubeMapFaces(2, GL_RGBA8, getWindowWidth() / 4, GL_RGBA, GL_UNSIGNED_BYTE,
                             pixelsGreen.data());
    
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        // The blue level 0 should be untouched by this since base level is 1.
        glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
    
        EXPECT_GL_NO_ERROR();
    
        // Draw using level 2. It should be set to red by GenerateMipmap.
        clearAndDrawQuad(mCubeProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
    
        // Observed incorrect rendering on NVIDIA, level zero seems to be incorrectly affected by
        // GenerateMipmap.
        // http://anglebug.com/3851
        ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsOpenGL());
    
        // Draw using level 0. It should still be blue.
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0);
        clearAndDrawQuad(mCubeProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    }
    
    // Create a texture with levels 0-2, call GenerateMipmap with max level 1 so that level 2 stays the
    // same, and then sample levels 1 and 2.
    // GLES 3.0.4 section 3.8.10:
    // "Mipmap generation replaces texel array levels levelbase + 1 through q with arrays derived from
    // the levelbase array, regardless of their previous contents. All other mipmap arrays, including
    // the levelbase array, are left unchanged by this computation."
    TEST_P(MipmapTestES3, GenerateMipmapMaxLevel)
    {
        glBindTexture(GL_TEXTURE_2D, mTexture);
    
        // Fill level 0 with blue
        std::vector<GLColor> pixelsBlue(getWindowWidth() * getWindowHeight(), GLColor::blue);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, pixelsBlue.data());
    
        // Fill level 1 with red
        std::vector<GLColor> pixelsRed(getWindowWidth() * getWindowHeight() / 4, GLColor::red);
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, getWindowWidth() / 2, getWindowHeight() / 2, 0,
                     GL_RGBA, GL_UNSIGNED_BYTE, pixelsRed.data());
    
        // Fill level 2 with green
        std::vector<GLColor> pixelsGreen(getWindowWidth() * getWindowHeight() / 16, GLColor::green);
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, getWindowWidth() / 4, getWindowHeight() / 4, 0,
                     GL_RGBA, GL_UNSIGNED_BYTE, pixelsGreen.data());
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        // The green level 2 should be untouched by this since max level is 1.
        glGenerateMipmap(GL_TEXTURE_2D);
    
        EXPECT_GL_NO_ERROR();
    
        // Draw using level 1. It should be set to blue by GenerateMipmap.
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 2, getWindowHeight() / 2);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 4, getWindowHeight() / 4, GLColor::blue);
    
        // Draw using level 2. It should still be green.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
        clearAndDrawQuad(m2DProgram, getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::green);
    }
    
    // Call GenerateMipmap with out-of-range base level. The spec is interpreted so that an out-of-range
    // base level does not have a color-renderable/texture-filterable internal format, so the
    // GenerateMipmap call generates INVALID_OPERATION. GLES 3.0.4 section 3.8.10:
    // "If the levelbase array was not specified with an unsized internal format from table 3.3 or a
    // sized internal format that is both color-renderable and texture-filterable according to table
    // 3.13, an INVALID_OPERATION error is generated."
    TEST_P(MipmapTestES3, GenerateMipmapBaseLevelOutOfRange)
    {
        glBindTexture(GL_TEXTURE_2D, mTexture);
    
        // Fill level 0 with blue
        std::vector<GLColor> pixelsBlue(getWindowWidth() * getWindowHeight(), GLColor::blue);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, pixelsBlue.data());
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1000);
    
        EXPECT_GL_NO_ERROR();
    
        // Expecting the out-of-range base level to be treated as not color-renderable and
        // texture-filterable.
        glGenerateMipmap(GL_TEXTURE_2D);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    
        // Draw using level 0. It should still be blue.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::blue);
    }
    
    // Call GenerateMipmap with out-of-range base level on an immutable texture. The base level should
    // be clamped, so the call doesn't generate an error.
    TEST_P(MipmapTestES3, GenerateMipmapBaseLevelOutOfRangeImmutableTexture)
    {
        glBindTexture(GL_TEXTURE_2D, mTexture);
    
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::green);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1000);
    
        EXPECT_GL_NO_ERROR();
    
        // This is essentially a no-op, since the texture only has one level.
        glGenerateMipmap(GL_TEXTURE_2D);
    
        EXPECT_GL_NO_ERROR();
    
        // The only level of the texture should still be green.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::green);
    }
    
    // A native version of the WebGL2 test tex-base-level-bug.html
    TEST_P(MipmapTestES3, BaseLevelTextureBug)
    {
        ANGLE_SKIP_TEST_IF(IsOpenGL() && IsAMD());
    
        // Regression in 10.12.4 needing workaround -- crbug.com/705865.
        // Seems to be passing on AMD GPUs. Definitely not NVIDIA.
        // Probably not Intel.
        ANGLE_SKIP_TEST_IF(IsOSX() && (IsNVIDIA() || IsIntel()));
    
        // TODO(cnorthrop): Figure out what's going on here: http://anglebug.com/3950
        ANGLE_SKIP_TEST_IF(IsVulkan());
    
        std::vector<GLColor> texDataRed(2u * 2u, GLColor::red);
    
        glBindTexture(GL_TEXTURE_2D, mTexture);
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataRed.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 2);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        ASSERT_GL_NO_ERROR();
    
        drawQuad(m2DProgram, "position", 0.5f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        ASSERT_GL_NO_ERROR();
    
        drawQuad(m2DProgram, "position", 0.5f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    }
    
    TEST_P(MipmapTestES31, MipmapWithMemoryBarrier)
    {
        std::vector<GLColor> pixelsRed(getWindowWidth() * getWindowHeight(), GLColor::red);
        std::vector<GLColor> pixelsGreen(getWindowWidth() * getWindowHeight() / 4, GLColor::green);
    
        constexpr char kVS[] = R"(#version 300 es
    precision highp float;
    in vec4 position;
    out vec2 texcoord;
    
    void main()
    {
        gl_Position = vec4(position.xy, 0.0, 1.0);
        texcoord = (position.xy * 0.5) + 0.5;
    })";
    
        constexpr char kFS[] = R"(#version 300 es
    precision highp float;
    uniform highp sampler2D tex;
    in vec2 texcoord;
    out vec4 out_FragColor;
    
    void main()
    {
        out_FragColor = texture(tex, texcoord);
    })";
    
        ANGLE_GL_PROGRAM(m2DProgram, kVS, kFS);
        glUseProgram(m2DProgram);
    
        // Create a texture with red and enable the mipmap
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        // Fill level 0 with red
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, pixelsRed.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        EXPECT_GL_NO_ERROR();
        glGenerateMipmap(GL_TEXTURE_2D);
        ASSERT_GL_NO_ERROR();
        // level 2 is red
        clearAndDrawQuad(m2DProgram.get(), getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::red);
    
        // Clear the level 1 to green
        glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, getWindowWidth() / 2, getWindowHeight() / 2, GL_RGBA,
                        GL_UNSIGNED_BYTE, pixelsGreen.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        EXPECT_GL_NO_ERROR();
        glGenerateMipmap(GL_TEXTURE_2D);
        ASSERT_GL_NO_ERROR();
        // Insert a memory barrier, then it will break the graph node submission order.
        glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        // level 0 is red
        clearAndDrawQuad(m2DProgram.get(), getWindowWidth(), getWindowHeight());
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        // Draw using level 2. It should be set to green by GenerateMipmap.
        clearAndDrawQuad(m2DProgram.get(), getWindowWidth() / 4, getWindowHeight() / 4);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 8, getWindowHeight() / 8, GLColor::green);
    }
    
    // Use this to select which configurations (e.g. which renderer, which GLES major version) these
    // tests should be run against.
    ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(MipmapTest);
    ANGLE_INSTANTIATE_TEST_ES3(MipmapTestES3);
    ANGLE_INSTANTIATE_TEST_ES31(MipmapTestES31);