Edit

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

Branch :

  • Show log

    Commit

  • Author : Yuly Novikov
    Date : 2021-03-08 17:32:25
    Hash : 57727c4e
    Message : Further suppress tests failing on Ozone. Since MipmapTest and Texture3DTestES2 fail in testSetUp(), it also needs to be skipped, in addition to the failing tests. Also suppress instantiation error for ES3-only QueryObjectTest Bug: angleproject:5725, angleproject:5728, angleproject:5704 Change-Id: Iac6549afd7609a2aee78bcfdb7c3de41886f3505 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2743920 Reviewed-by: Yuly Novikov <ynovikov@chromium.org> Commit-Queue: Yuly Novikov <ynovikov@chromium.org>

  • src/tests/gl_tests/TextureTest.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 "common/mathutil.h"
    #include "test_utils/ANGLETest.h"
    #include "test_utils/gl_raii.h"
    
    using namespace angle;
    
    namespace
    {
    
    constexpr GLuint kPixelTolerance     = 1u;
    constexpr GLfloat kPixelTolerance32F = 0.01f;
    
    // Single compressed ETC2 block of source pixels all set red
    constexpr uint8_t kCompressedImageETC2[] = {0x7E, 0x80, 0x04, 0x7F, 0x00, 0x07, 0xE0, 0x00};
    
    // Take a pixel, and reset the components not covered by the format to default
    // values. In particular, the default value for the alpha component is 255
    // (1.0 as unsigned normalized fixed point value).
    // For legacy formats, the components may be reordered to match the color that
    // would be created if a pixel of that format was initialized from the given color
    GLColor SliceFormatColor(GLenum format, GLColor full)
    {
        switch (format)
        {
            case GL_RED:
                return GLColor(full.R, 0, 0, 255u);
            case GL_RG:
                return GLColor(full.R, full.G, 0, 255u);
            case GL_RGB:
                return GLColor(full.R, full.G, full.B, 255u);
            case GL_RGBA:
                return full;
            case GL_LUMINANCE:
                return GLColor(full.R, full.R, full.R, 255u);
            case GL_ALPHA:
                return GLColor(0, 0, 0, full.R);
            case GL_LUMINANCE_ALPHA:
                return GLColor(full.R, full.R, full.R, full.G);
            default:
                EXPECT_TRUE(false);
                return GLColor::white;
        }
    }
    
    GLColor16UI SliceFormatColor16UI(GLenum format, GLColor16UI full)
    {
        switch (format)
        {
            case GL_RED:
                return GLColor16UI(full.R, 0, 0, 0xFFFF);
            case GL_RG:
                return GLColor16UI(full.R, full.G, 0, 0xFFFF);
            case GL_RGB:
                return GLColor16UI(full.R, full.G, full.B, 0xFFFF);
            case GL_RGBA:
                return full;
            case GL_LUMINANCE:
                return GLColor16UI(full.R, full.R, full.R, 0xFFFF);
            case GL_ALPHA:
                return GLColor16UI(0, 0, 0, full.R);
            case GL_LUMINANCE_ALPHA:
                return GLColor16UI(full.R, full.R, full.R, full.G);
            default:
                EXPECT_TRUE(false);
                return GLColor16UI(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF);
        }
    }
    
    // As above, for 32F colors
    GLColor32F SliceFormatColor32F(GLenum format, GLColor32F full)
    {
        switch (format)
        {
            case GL_RED:
                return GLColor32F(full.R, 0.0f, 0.0f, 1.0f);
            case GL_RG:
                return GLColor32F(full.R, full.G, 0.0f, 1.0f);
            case GL_RGB:
                return GLColor32F(full.R, full.G, full.B, 1.0f);
            case GL_RGBA:
                return full;
            case GL_LUMINANCE:
                return GLColor32F(full.R, full.R, full.R, 1.0f);
            case GL_ALPHA:
                return GLColor32F(0.0f, 0.0f, 0.0f, full.R);
            case GL_LUMINANCE_ALPHA:
                return GLColor32F(full.R, full.R, full.R, full.G);
            default:
                EXPECT_TRUE(false);
                return GLColor32F(1.0f, 1.0f, 1.0f, 1.0f);
        }
    }
    
    class TexCoordDrawTest : public ANGLETest
    {
      protected:
        TexCoordDrawTest() : ANGLETest(), mProgram(0), mFramebuffer(0), mFramebufferColorTexture(0)
        {
            setWindowWidth(128);
            setWindowHeight(128);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
        }
    
        virtual const char *getVertexShaderSource()
        {
            return R"(precision highp float;
    attribute vec4 position;
    varying vec2 texcoord;
    
    void main()
    {
        gl_Position = vec4(position.xy, 0.0, 1.0);
        texcoord = (position.xy * 0.5) + 0.5;
    })";
        }
    
        virtual const char *getFragmentShaderSource() = 0;
    
        virtual void setUpProgram()
        {
            const char *vertexShaderSource   = getVertexShaderSource();
            const char *fragmentShaderSource = getFragmentShaderSource();
    
            mProgram = CompileProgram(vertexShaderSource, fragmentShaderSource);
            ASSERT_NE(0u, mProgram);
            ASSERT_GL_NO_ERROR();
        }
    
        void testSetUp() override { setUpFramebuffer(); }
    
        void testTearDown() override
        {
            glBindFramebuffer(GL_FRAMEBUFFER, 0);
            glDeleteFramebuffers(1, &mFramebuffer);
            glDeleteTextures(1, &mFramebufferColorTexture);
            glDeleteProgram(mProgram);
        }
    
        void setUpFramebuffer()
        {
            // We use an FBO to work around an issue where the default framebuffer applies SRGB
            // conversion (particularly known to happen incorrectly on Intel GL drivers). It's not
            // clear whether this issue can even be fixed on all backends. For example GLES 3.0.4 spec
            // section 4.4 says that the format of the default framebuffer is entirely up to the window
            // system, so it might be SRGB, and GLES 3.0 doesn't have a "FRAMEBUFFER_SRGB" to turn off
            // SRGB conversion like desktop GL does.
            // TODO(oetuaho): Get rid of this if the underlying issue is fixed.
            glGenFramebuffers(1, &mFramebuffer);
            glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
    
            glGenTextures(1, &mFramebufferColorTexture);
            glBindTexture(GL_TEXTURE_2D, mFramebufferColorTexture);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                         GL_UNSIGNED_BYTE, nullptr);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                   mFramebufferColorTexture, 0);
            ASSERT_GL_NO_ERROR();
            ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
            glBindTexture(GL_TEXTURE_2D, 0);
        }
    
        // Returns the created texture ID.
        GLuint create2DTexture()
        {
            GLuint texture2D;
            glGenTextures(1, &texture2D);
            glBindTexture(GL_TEXTURE_2D, texture2D);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
            EXPECT_GL_NO_ERROR();
            return texture2D;
        }
    
        GLuint mProgram;
        GLuint mFramebuffer;
    
      private:
        GLuint mFramebufferColorTexture;
    };
    
    class Texture2DTest : public TexCoordDrawTest
    {
      protected:
        Texture2DTest() : TexCoordDrawTest(), mTexture2D(0), mTexture2DUniformLocation(-1) {}
    
        const char *getFragmentShaderSource() override
        {
            return R"(precision highp float;
    uniform sampler2D tex;
    varying vec2 texcoord;
    
    void main()
    {
        gl_FragColor = texture2D(tex, texcoord);
    })";
        }
    
        virtual const char *getTextureUniformName() { return "tex"; }
    
        void setUpProgram() override
        {
            TexCoordDrawTest::setUpProgram();
            mTexture2DUniformLocation = glGetUniformLocation(mProgram, getTextureUniformName());
            ASSERT_NE(-1, mTexture2DUniformLocation);
        }
    
        void testSetUp() override
        {
            TexCoordDrawTest::testSetUp();
            mTexture2D = create2DTexture();
    
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mTexture2D);
            TexCoordDrawTest::testTearDown();
        }
    
        // Tests CopyTexSubImage with floating point textures of various formats.
        void testFloatCopySubImage(int sourceImageChannels, int destImageChannels)
        {
            setUpProgram();
    
            if (getClientMajorVersion() < 3)
            {
                ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage") ||
                                   !IsGLExtensionEnabled("GL_OES_texture_float"));
    
                ANGLE_SKIP_TEST_IF((sourceImageChannels < 3 || destImageChannels < 3) &&
                                   !IsGLExtensionEnabled("GL_EXT_texture_rg"));
    
                ANGLE_SKIP_TEST_IF(destImageChannels == 3 &&
                                   !IsGLExtensionEnabled("GL_CHROMIUM_color_buffer_float_rgb"));
    
                ANGLE_SKIP_TEST_IF(destImageChannels == 4 &&
                                   !IsGLExtensionEnabled("GL_CHROMIUM_color_buffer_float_rgba"));
    
                ANGLE_SKIP_TEST_IF(destImageChannels <= 2);
            }
            else
            {
                ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_color_buffer_float"));
    
                ANGLE_SKIP_TEST_IF(destImageChannels == 3 &&
                                   !IsGLExtensionEnabled("GL_CHROMIUM_color_buffer_float_rgb"));
            }
    
            // clang-format off
            GLfloat sourceImageData[4][16] =
            {
                { // R
                    1.0f,
                    0.0f,
                    0.0f,
                    1.0f
                },
                { // RG
                    1.0f, 0.0f,
                    0.0f, 1.0f,
                    0.0f, 0.0f,
                    1.0f, 1.0f
                },
                { // RGB
                    1.0f, 0.0f, 0.0f,
                    0.0f, 1.0f, 0.0f,
                    0.0f, 0.0f, 1.0f,
                    1.0f, 1.0f, 0.0f
                },
                { // RGBA
                    1.0f, 0.0f, 0.0f, 1.0f,
                    0.0f, 1.0f, 0.0f, 1.0f,
                    0.0f, 0.0f, 1.0f, 1.0f,
                    1.0f, 1.0f, 0.0f, 1.0f
                },
            };
            // clang-format on
    
            GLenum imageFormats[] = {
                GL_R32F,
                GL_RG32F,
                GL_RGB32F,
                GL_RGBA32F,
            };
    
            GLenum sourceUnsizedFormats[] = {
                GL_RED,
                GL_RG,
                GL_RGB,
                GL_RGBA,
            };
    
            GLuint textures[2];
    
            glGenTextures(2, textures);
    
            GLfloat *imageData         = sourceImageData[sourceImageChannels - 1];
            GLenum sourceImageFormat   = imageFormats[sourceImageChannels - 1];
            GLenum sourceUnsizedFormat = sourceUnsizedFormats[sourceImageChannels - 1];
            GLenum destImageFormat     = imageFormats[destImageChannels - 1];
    
            glBindTexture(GL_TEXTURE_2D, textures[0]);
            if (getClientMajorVersion() >= 3)
            {
                glTexStorage2D(GL_TEXTURE_2D, 1, sourceImageFormat, 2, 2);
            }
            else
            {
                glTexStorage2DEXT(GL_TEXTURE_2D, 1, sourceImageFormat, 2, 2);
            }
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, sourceUnsizedFormat, GL_FLOAT, imageData);
    
            if (sourceImageChannels < 3 && !IsGLExtensionEnabled("GL_EXT_texture_rg"))
            {
                // This is not supported
                ASSERT_GL_ERROR(GL_INVALID_OPERATION);
            }
            else
            {
                ASSERT_GL_NO_ERROR();
            }
    
            GLuint fbo;
            glGenFramebuffers(1, &fbo);
            glBindFramebuffer(GL_FRAMEBUFFER, fbo);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[0], 0);
    
            glBindTexture(GL_TEXTURE_2D, textures[1]);
            if (getClientMajorVersion() >= 3)
            {
                glTexStorage2D(GL_TEXTURE_2D, 1, destImageFormat, 2, 2);
            }
            else
            {
                glTexStorage2DEXT(GL_TEXTURE_2D, 1, destImageFormat, 2, 2);
            }
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
            glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 2);
            ASSERT_GL_NO_ERROR();
    
            glBindFramebuffer(GL_FRAMEBUFFER, 0);
            drawQuad(mProgram, "position", 0.5f);
    
            int testImageChannels = std::min(sourceImageChannels, destImageChannels);
    
            EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
            if (testImageChannels > 1)
            {
                EXPECT_PIXEL_EQ(getWindowHeight() - 1, 0, 0, 255, 0, 255);
                EXPECT_PIXEL_EQ(getWindowHeight() - 1, getWindowWidth() - 1, 255, 255, 0, 255);
                if (testImageChannels > 2)
                {
                    EXPECT_PIXEL_EQ(0, getWindowWidth() - 1, 0, 0, 255, 255);
                }
            }
    
            glDeleteFramebuffers(1, &fbo);
            glDeleteTextures(2, textures);
    
            ASSERT_GL_NO_ERROR();
        }
    
        GLuint mTexture2D;
        GLint mTexture2DUniformLocation;
    };
    
    class Texture2DTestES3 : public Texture2DTest
    {
      protected:
        Texture2DTestES3() : Texture2DTest() {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "uniform highp sampler2D tex;\n"
                   "in vec2 texcoord;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    fragColor = texture(tex, texcoord);\n"
                   "}\n";
        }
    
        void testSetUp() override
        {
            Texture2DTest::testSetUp();
            setUpProgram();
        }
    };
    
    class Texture2DBaseMaxTestES3 : public ANGLETest
    {
      protected:
        static constexpr size_t kMip0Size   = 13;
        static constexpr uint32_t kMipCount = 4;
    
        Texture2DBaseMaxTestES3() : ANGLETest(), mTextureLocation(0), mLodLocation(0)
        {
            setWindowWidth(128);
            setWindowHeight(128);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
        }
    
        static constexpr size_t getMipDataSize(size_t mip0Size, size_t mip)
        {
            size_t mipSize = std::max<size_t>(1u, mip0Size >> mip);
            return mipSize * mipSize;
        }
    
        static constexpr size_t getTotalMipDataSize(size_t mip0Size)
        {
            size_t totalCount = 0;
            for (size_t mip = 0; mip < kMipCount; ++mip)
            {
                totalCount += getMipDataSize(mip0Size, mip);
            }
            return totalCount;
        }
    
        static constexpr size_t getMipDataOffset(size_t mip0Size, size_t mip)
        {
            // This calculates:
            //
            //     mip == 0: 0
            //     o.w.:     sum(0, mip-1) getMipDataSize(i)
            //
            // The above can be calculated simply as:
            //
            //     (mip0 >> (kMipCount-1))^2 * (0x55555555 & ((1 << (2*mip)) - 1))
            //     \__________  ___________/   \_______________  ________________/
            //                \/                               \/
            //          last mip size                 sum(0, mip-1) (4^i)
            //
            // But let's loop explicitly for clarity.
            size_t offset = 0;
            for (size_t m = 0; m < mip; ++m)
            {
                offset += getMipDataSize(mip0Size, m);
            }
            return offset;
        }
    
        template <typename colorType = GLColor>
        void fillMipData(colorType *data, size_t mip0Size, const colorType mipColors[kMipCount])
        {
            for (size_t mip = 0; mip < kMipCount; ++mip)
            {
                size_t offset = getMipDataOffset(mip0Size, mip);
                size_t size   = getMipDataSize(mip0Size, mip);
                std::fill(data + offset, data + offset + size, mipColors[mip]);
            }
        }
    
        void initTest()
        {
            // Set up program to sample from specific lod level.
            mProgram.makeRaster(essl3_shaders::vs::Texture2DLod(), essl3_shaders::fs::Texture2DLod());
            ASSERT(mProgram.valid());
    
            glUseProgram(mProgram);
    
            mTextureLocation = glGetUniformLocation(mProgram, essl3_shaders::Texture2DUniform());
            ASSERT_NE(-1, mTextureLocation);
    
            mLodLocation = glGetUniformLocation(mProgram, essl3_shaders::LodUniform());
            ASSERT_NE(-1, mLodLocation);
    
            // Set up texture with a handful of lods.
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, mTexture);
    
            std::array<GLColor, getTotalMipDataSize(kMip0Size)> mipData;
            fillMipData(mipData.data(), kMip0Size, kMipColors);
    
            for (size_t mip = 0; mip < kMipCount; ++mip)
            {
                glTexImage2D(GL_TEXTURE_2D, mip, GL_RGBA8, kMip0Size >> mip, kMip0Size >> mip, 0,
                             GL_RGBA, GL_UNSIGNED_BYTE,
                             mipData.data() + getMipDataOffset(kMip0Size, mip));
            }
    
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
            EXPECT_GL_NO_ERROR();
        }
    
        void setLodUniform(uint32_t lod) { glUniform1f(mLodLocation, lod); }
    
        GLProgram mProgram;
        GLTexture mTexture;
        GLint mTextureLocation;
        GLint mLodLocation;
    
        const GLColor kMipColors[kMipCount] = {
            GLColor::red,
            GLColor::green,
            GLColor::blue,
            GLColor::magenta,
        };
    };
    
    class TextureES31PPO
    {
      protected:
        TextureES31PPO() : mVertProg(0), mFragProg(0), mPipeline(0) {}
    
        const char *get2DTexturedVertexShaderSource()
        {
            return "#version 310 es\n"
                   "precision mediump float;\n"
                   "in vec2 position;\n"
                   "out vec2 texCoord;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position, 0, 1);\n"
                   "    texCoord = position * 0.5 + vec2(0.5);\n"
                   "}";
        }
    
        const char *get2DTexturedFragmentShaderSource()
        {
            return "#version 310 es\n"
                   "precision mediump float;\n"
                   "in vec2 texCoord;\n"
                   "uniform sampler2D tex1;\n"
                   "uniform sampler2D tex2;\n"
                   "uniform sampler2D tex3;\n"
                   "uniform sampler2D tex4;\n"
                   "out vec4 color;\n"
                   "void main()\n"
                   "{\n"
                   "    color = (texture(tex1, texCoord) + texture(tex2, texCoord) \n"
                   "          +  texture(tex3, texCoord) + texture(tex4, texCoord)) * 0.25;\n"
                   "}";
        }
    
        void bindProgramPipeline(const GLchar *vertString, const GLchar *fragString)
        {
            mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertString);
            ASSERT_NE(mVertProg, 0u);
            mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragString);
            ASSERT_NE(mFragProg, 0u);
    
            // Generate a program pipeline and attach the programs to their respective stages
            glGenProgramPipelines(1, &mPipeline);
            EXPECT_GL_NO_ERROR();
            glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg);
            EXPECT_GL_NO_ERROR();
            glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg);
            EXPECT_GL_NO_ERROR();
            glBindProgramPipeline(mPipeline);
            EXPECT_GL_NO_ERROR();
        }
    
        void bind2DTexturedQuadProgramPipeline()
        {
            const char *vertexShaderSource   = get2DTexturedVertexShaderSource();
            const char *fragmentShaderSource = get2DTexturedFragmentShaderSource();
    
            m2DTexturedQuadVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertexShaderSource);
            ASSERT_NE(m2DTexturedQuadVertProg, 0u);
            m2DTexturedQuadFragProg =
                glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragmentShaderSource);
            ASSERT_NE(m2DTexturedQuadFragProg, 0u);
    
            // Generate a program pipeline and attach the programs to their respective stages
            glGenProgramPipelines(1, &m2DTexturedQuadPipeline);
            EXPECT_GL_NO_ERROR();
            glUseProgramStages(m2DTexturedQuadPipeline, GL_VERTEX_SHADER_BIT, m2DTexturedQuadVertProg);
            EXPECT_GL_NO_ERROR();
            glUseProgramStages(m2DTexturedQuadPipeline, GL_FRAGMENT_SHADER_BIT,
                               m2DTexturedQuadFragProg);
            EXPECT_GL_NO_ERROR();
            glBindProgramPipeline(m2DTexturedQuadPipeline);
            EXPECT_GL_NO_ERROR();
        }
    
        void ppoDrawQuad(std::array<Vector3, 6> &quadVertices,
                         const std::string &positionAttribName,
                         const GLfloat positionAttribZ,
                         const GLfloat positionAttribXYScale)
        {
            glUseProgram(0);
    
            for (Vector3 &vertex : quadVertices)
            {
                vertex.x() *= positionAttribXYScale;
                vertex.y() *= positionAttribXYScale;
                vertex.z() = positionAttribZ;
            }
    
            GLint positionLocation = glGetAttribLocation(mVertProg, positionAttribName.c_str());
    
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
            glEnableVertexAttribArray(positionLocation);
    
            glDrawArrays(GL_TRIANGLES, 0, 6);
    
            glDisableVertexAttribArray(positionLocation);
            glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
        }
    
        GLuint mVertProg;
        GLuint mFragProg;
        GLuint mPipeline;
        GLuint m2DTexturedQuadVertProg;
        GLuint m2DTexturedQuadFragProg;
        GLuint m2DTexturedQuadPipeline;
    };
    
    class Texture2DTestES31PPO : public TextureES31PPO, public Texture2DTest
    {
      protected:
        Texture2DTestES31PPO() : TextureES31PPO(), Texture2DTest() {}
    
        void testSetUp() override { Texture2DTest::testSetUp(); }
    };
    
    class Texture2DIntegerAlpha1TestES3 : public Texture2DTest
    {
      protected:
        Texture2DIntegerAlpha1TestES3() : Texture2DTest() {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "uniform highp isampler2D tex;\n"
                   "in vec2 texcoord;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    vec4 green = vec4(0, 1, 0, 1);\n"
                   "    vec4 black = vec4(0, 0, 0, 0);\n"
                   "    fragColor = (texture(tex, texcoord).a == 1) ? green : black;\n"
                   "}\n";
        }
    
        void testSetUp() override
        {
            Texture2DTest::testSetUp();
            setUpProgram();
        }
    };
    
    class Texture2DUnsignedIntegerAlpha1TestES3 : public Texture2DTest
    {
      protected:
        Texture2DUnsignedIntegerAlpha1TestES3() : Texture2DTest() {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "uniform highp usampler2D tex;\n"
                   "in vec2 texcoord;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    vec4 green = vec4(0, 1, 0, 1);\n"
                   "    vec4 black = vec4(0, 0, 0, 0);\n"
                   "    fragColor = (texture(tex, texcoord).a == 1u) ? green : black;\n"
                   "}\n";
        }
    
        void testSetUp() override
        {
            Texture2DTest::testSetUp();
            setUpProgram();
        }
    };
    
    class Texture2DTestWithDrawScale : public Texture2DTest
    {
      protected:
        Texture2DTestWithDrawScale() : Texture2DTest(), mDrawScaleUniformLocation(-1) {}
    
        const char *getVertexShaderSource() override
        {
            return
                R"(precision highp float;
                attribute vec4 position;
                varying vec2 texcoord;
    
                uniform vec2 drawScale;
    
                void main()
                {
                    gl_Position = vec4(position.xy * drawScale, 0.0, 1.0);
                    texcoord = (position.xy * 0.5) + 0.5;
                })";
        }
    
        void testSetUp() override
        {
            Texture2DTest::testSetUp();
    
            setUpProgram();
    
            mDrawScaleUniformLocation = glGetUniformLocation(mProgram, "drawScale");
            ASSERT_NE(-1, mDrawScaleUniformLocation);
    
            glUseProgram(mProgram);
            glUniform2f(mDrawScaleUniformLocation, 1.0f, 1.0f);
            glUseProgram(0);
            ASSERT_GL_NO_ERROR();
        }
    
        GLint mDrawScaleUniformLocation;
    };
    
    class Sampler2DAsFunctionParameterTest : public Texture2DTest
    {
      protected:
        Sampler2DAsFunctionParameterTest() : Texture2DTest() {}
    
        const char *getFragmentShaderSource() override
        {
            return
                R"(precision highp float;
                uniform sampler2D tex;
                varying vec2 texcoord;
    
                vec4 computeFragColor(sampler2D aTex)
                {
                    return texture2D(aTex, texcoord);
                }
    
                void main()
                {
                    gl_FragColor = computeFragColor(tex);
                })";
        }
    
        void testSetUp() override
        {
            Texture2DTest::testSetUp();
            setUpProgram();
        }
    };
    
    class TextureCubeTest : public TexCoordDrawTest
    {
      protected:
        TextureCubeTest()
            : TexCoordDrawTest(),
              mTexture2D(0),
              mTextureCube(0),
              mTexture2DUniformLocation(-1),
              mTextureCubeUniformLocation(-1)
        {}
    
        const char *getFragmentShaderSource() override
        {
            return
                R"(precision highp float;
                uniform sampler2D tex2D;
                uniform samplerCube texCube;
                varying vec2 texcoord;
    
                void main()
                {
                    gl_FragColor = texture2D(tex2D, texcoord);
                    gl_FragColor += textureCube(texCube, vec3(texcoord, 0));
                })";
        }
    
        void testSetUp() override
        {
            TexCoordDrawTest::testSetUp();
    
            glGenTextures(1, &mTextureCube);
            glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
            for (GLenum face = 0; face < 6; face++)
            {
                glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA, 1, 1, 0, GL_RGBA,
                             GL_UNSIGNED_BYTE, nullptr);
            }
            EXPECT_GL_NO_ERROR();
    
            mTexture2D = create2DTexture();
    
            setUpProgram();
    
            mTexture2DUniformLocation = glGetUniformLocation(mProgram, "tex2D");
            ASSERT_NE(-1, mTexture2DUniformLocation);
            mTextureCubeUniformLocation = glGetUniformLocation(mProgram, "texCube");
            ASSERT_NE(-1, mTextureCubeUniformLocation);
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mTextureCube);
            TexCoordDrawTest::testTearDown();
        }
    
        GLuint mTexture2D;
        GLuint mTextureCube;
        GLint mTexture2DUniformLocation;
        GLint mTextureCubeUniformLocation;
    };
    
    class TextureCubeTestES3 : public ANGLETest
    {
      protected:
        TextureCubeTestES3() {}
    };
    
    class SamplerArrayTest : public TexCoordDrawTest
    {
      protected:
        SamplerArrayTest()
            : TexCoordDrawTest(),
              mTexture2DA(0),
              mTexture2DB(0),
              mTexture0UniformLocation(-1),
              mTexture1UniformLocation(-1)
        {}
    
        const char *getFragmentShaderSource() override
        {
            return
                R"(precision mediump float;
                uniform highp sampler2D tex2DArray[2];
                varying vec2 texcoord;
                void main()
                {
                    gl_FragColor = texture2D(tex2DArray[0], texcoord);
                    gl_FragColor += texture2D(tex2DArray[1], texcoord);
                })";
        }
    
        void testSetUp() override
        {
            TexCoordDrawTest::testSetUp();
    
            setUpProgram();
    
            mTexture0UniformLocation = glGetUniformLocation(mProgram, "tex2DArray[0]");
            ASSERT_NE(-1, mTexture0UniformLocation);
            mTexture1UniformLocation = glGetUniformLocation(mProgram, "tex2DArray[1]");
            ASSERT_NE(-1, mTexture1UniformLocation);
    
            mTexture2DA = create2DTexture();
            mTexture2DB = create2DTexture();
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mTexture2DA);
            glDeleteTextures(1, &mTexture2DB);
            TexCoordDrawTest::testTearDown();
        }
    
        void testSamplerArrayDraw()
        {
            GLubyte texData[4];
            texData[0] = 0;
            texData[1] = 60;
            texData[2] = 0;
            texData[3] = 255;
    
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, mTexture2DA);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    
            texData[1] = 120;
            glActiveTexture(GL_TEXTURE1);
            glBindTexture(GL_TEXTURE_2D, mTexture2DB);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, texData);
            EXPECT_GL_ERROR(GL_NO_ERROR);
    
            glUseProgram(mProgram);
            glUniform1i(mTexture0UniformLocation, 0);
            glUniform1i(mTexture1UniformLocation, 1);
            drawQuad(mProgram, "position", 0.5f);
            EXPECT_GL_NO_ERROR();
    
            EXPECT_PIXEL_NEAR(0, 0, 0, 180, 0, 255, 2);
        }
    
        GLuint mTexture2DA;
        GLuint mTexture2DB;
        GLint mTexture0UniformLocation;
        GLint mTexture1UniformLocation;
    };
    
    class SamplerArrayAsFunctionParameterTest : public SamplerArrayTest
    {
      protected:
        SamplerArrayAsFunctionParameterTest() : SamplerArrayTest() {}
    
        const char *getFragmentShaderSource() override
        {
            return
                R"(precision mediump float;
                uniform highp sampler2D tex2DArray[2];
                varying vec2 texcoord;
    
                vec4 computeFragColor(highp sampler2D aTex2DArray[2])
                {
                    return texture2D(aTex2DArray[0], texcoord) + texture2D(aTex2DArray[1], texcoord);
                }
    
                void main()
                {
                    gl_FragColor = computeFragColor(tex2DArray);
                })";
        }
    };
    
    class Texture2DArrayTestES3 : public TexCoordDrawTest
    {
      protected:
        Texture2DArrayTestES3() : TexCoordDrawTest(), m2DArrayTexture(0), mTextureArrayLocation(-1) {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "uniform highp sampler2DArray tex2DArray;\n"
                   "in vec2 texcoord;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    fragColor = texture(tex2DArray, vec3(texcoord.x, texcoord.y, 0.0));\n"
                   "}\n";
        }
    
        void testSetUp() override
        {
            TexCoordDrawTest::testSetUp();
    
            setUpProgram();
    
            mTextureArrayLocation = glGetUniformLocation(mProgram, "tex2DArray");
            ASSERT_NE(-1, mTextureArrayLocation);
    
            glGenTextures(1, &m2DArrayTexture);
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &m2DArrayTexture);
            TexCoordDrawTest::testTearDown();
        }
    
        GLuint m2DArrayTexture;
        GLint mTextureArrayLocation;
    };
    
    class TextureSizeTextureArrayTest : public TexCoordDrawTest
    {
      protected:
        TextureSizeTextureArrayTest()
            : TexCoordDrawTest(),
              mTexture2DA(0),
              mTexture2DB(0),
              mTexture0Location(-1),
              mTexture1Location(-1)
        {}
    
        const char *getVertexShaderSource() override { return essl3_shaders::vs::Simple(); }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "uniform highp sampler2D tex2DArray[2];\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    float red = float(textureSize(tex2DArray[0], 0).x) / 255.0;\n"
                   "    float green = float(textureSize(tex2DArray[1], 0).x) / 255.0;\n"
                   "    fragColor = vec4(red, green, 0.0, 1.0);\n"
                   "}\n";
        }
    
        void testSetUp() override
        {
            TexCoordDrawTest::testSetUp();
    
            setUpProgram();
    
            mTexture0Location = glGetUniformLocation(mProgram, "tex2DArray[0]");
            ASSERT_NE(-1, mTexture0Location);
            mTexture1Location = glGetUniformLocation(mProgram, "tex2DArray[1]");
            ASSERT_NE(-1, mTexture1Location);
    
            mTexture2DA = create2DTexture();
            mTexture2DB = create2DTexture();
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mTexture2DA);
            glDeleteTextures(1, &mTexture2DB);
            TexCoordDrawTest::testTearDown();
        }
    
        GLuint mTexture2DA;
        GLuint mTexture2DB;
        GLint mTexture0Location;
        GLint mTexture1Location;
    };
    
    // Test for GL_OES_texture_3D extension
    class Texture3DTestES2 : public TexCoordDrawTest
    {
      protected:
        Texture3DTestES2() : TexCoordDrawTest(), mTexture3D(0), mTexture3DUniformLocation(-1) {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 100\n"
                   "varying vec2 texcoord;\n"
                   "attribute vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            if (!hasTexture3DExt())
            {
                return "#version 100\n"
                       "precision highp float;\n"
                       "varying vec2 texcoord;\n"
                       "void main()\n"
                       "{\n"
                       "    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
                       "}\n";
            }
            return "#version 100\n"
                   "#extension GL_OES_texture_3D : enable\n"
                   "precision highp float;\n"
                   "uniform highp sampler3D tex3D;\n"
                   "uniform highp float level;\n"
                   "varying vec2 texcoord;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_FragColor = texture3DLod(tex3D, vec3(texcoord, 0.0), level);\n"
                   "}\n";
        }
    
        void testSetUp() override
        {
            // http://anglebug.com/5728
            ANGLE_SKIP_TEST_IF(IsOzone());
    
            TexCoordDrawTest::testSetUp();
    
            glGenTextures(1, &mTexture3D);
    
            setUpProgram();
    
            mTexture3DUniformLocation = glGetUniformLocation(mProgram, "tex3D");
            if (hasTexture3DExt())
            {
                ASSERT_NE(-1, mTexture3DUniformLocation);
            }
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mTexture3D);
            TexCoordDrawTest::testTearDown();
        }
    
        bool hasTexture3DExt() const
        {
            // http://anglebug.com/4927
            if ((IsPixel2() || IsNexus5X()) && IsOpenGLES())
            {
                return false;
            }
            return IsGLExtensionEnabled("GL_OES_texture_3D");
        }
    
        GLuint mTexture3D;
        GLint mTexture3DUniformLocation;
    };
    
    class Texture3DTestES3 : public Texture3DTestES2
    {
      protected:
        Texture3DTestES3() : Texture3DTestES2() {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "uniform highp sampler3D tex3D;\n"
                   "in vec2 texcoord;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    fragColor = texture(tex3D, vec3(texcoord, 0.0));\n"
                   "}\n";
        }
    };
    
    class ShadowSamplerPlusSampler3DTestES3 : public TexCoordDrawTest
    {
      protected:
        ShadowSamplerPlusSampler3DTestES3()
            : TexCoordDrawTest(),
              mTextureShadow(0),
              mTexture3D(0),
              mTextureShadowUniformLocation(-1),
              mTexture3DUniformLocation(-1),
              mDepthRefUniformLocation(-1)
        {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "uniform highp sampler2DShadow tex2DShadow;\n"
                   "uniform highp sampler3D tex3D;\n"
                   "in vec2 texcoord;\n"
                   "uniform float depthRef;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    fragColor = vec4(texture(tex2DShadow, vec3(texcoord, depthRef)) * 0.5);\n"
                   "    fragColor += texture(tex3D, vec3(texcoord, 0.0));\n"
                   "}\n";
        }
    
        void testSetUp() override
        {
            TexCoordDrawTest::testSetUp();
    
            glGenTextures(1, &mTexture3D);
    
            glGenTextures(1, &mTextureShadow);
            glBindTexture(GL_TEXTURE_2D, mTextureShadow);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
    
            setUpProgram();
    
            mTextureShadowUniformLocation = glGetUniformLocation(mProgram, "tex2DShadow");
            ASSERT_NE(-1, mTextureShadowUniformLocation);
            mTexture3DUniformLocation = glGetUniformLocation(mProgram, "tex3D");
            ASSERT_NE(-1, mTexture3DUniformLocation);
            mDepthRefUniformLocation = glGetUniformLocation(mProgram, "depthRef");
            ASSERT_NE(-1, mDepthRefUniformLocation);
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mTextureShadow);
            glDeleteTextures(1, &mTexture3D);
            TexCoordDrawTest::testTearDown();
        }
    
        GLuint mTextureShadow;
        GLuint mTexture3D;
        GLint mTextureShadowUniformLocation;
        GLint mTexture3DUniformLocation;
        GLint mDepthRefUniformLocation;
    };
    
    class SamplerTypeMixTestES3 : public TexCoordDrawTest
    {
      protected:
        SamplerTypeMixTestES3()
            : TexCoordDrawTest(),
              mTexture2D(0),
              mTextureCube(0),
              mTexture2DShadow(0),
              mTextureCubeShadow(0),
              mTexture2DUniformLocation(-1),
              mTextureCubeUniformLocation(-1),
              mTexture2DShadowUniformLocation(-1),
              mTextureCubeShadowUniformLocation(-1),
              mDepthRefUniformLocation(-1)
        {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "uniform highp sampler2D tex2D;\n"
                   "uniform highp samplerCube texCube;\n"
                   "uniform highp sampler2DShadow tex2DShadow;\n"
                   "uniform highp samplerCubeShadow texCubeShadow;\n"
                   "in vec2 texcoord;\n"
                   "uniform float depthRef;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    fragColor = texture(tex2D, texcoord);\n"
                   "    fragColor += texture(texCube, vec3(1.0, 0.0, 0.0));\n"
                   "    fragColor += vec4(texture(tex2DShadow, vec3(texcoord, depthRef)) * 0.25);\n"
                   "    fragColor += vec4(texture(texCubeShadow, vec4(1.0, 0.0, 0.0, depthRef)) * "
                   "0.125);\n"
                   "}\n";
        }
    
        void testSetUp() override
        {
            TexCoordDrawTest::testSetUp();
    
            glGenTextures(1, &mTexture2D);
            glGenTextures(1, &mTextureCube);
    
            glGenTextures(1, &mTexture2DShadow);
            glBindTexture(GL_TEXTURE_2D, mTexture2DShadow);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
    
            glGenTextures(1, &mTextureCubeShadow);
            glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCubeShadow);
            glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
    
            setUpProgram();
    
            mTexture2DUniformLocation = glGetUniformLocation(mProgram, "tex2D");
            ASSERT_NE(-1, mTexture2DUniformLocation);
            mTextureCubeUniformLocation = glGetUniformLocation(mProgram, "texCube");
            ASSERT_NE(-1, mTextureCubeUniformLocation);
            mTexture2DShadowUniformLocation = glGetUniformLocation(mProgram, "tex2DShadow");
            ASSERT_NE(-1, mTexture2DShadowUniformLocation);
            mTextureCubeShadowUniformLocation = glGetUniformLocation(mProgram, "texCubeShadow");
            ASSERT_NE(-1, mTextureCubeShadowUniformLocation);
            mDepthRefUniformLocation = glGetUniformLocation(mProgram, "depthRef");
            ASSERT_NE(-1, mDepthRefUniformLocation);
    
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mTexture2D);
            glDeleteTextures(1, &mTextureCube);
            glDeleteTextures(1, &mTexture2DShadow);
            glDeleteTextures(1, &mTextureCubeShadow);
            TexCoordDrawTest::testTearDown();
        }
    
        GLuint mTexture2D;
        GLuint mTextureCube;
        GLuint mTexture2DShadow;
        GLuint mTextureCubeShadow;
        GLint mTexture2DUniformLocation;
        GLint mTextureCubeUniformLocation;
        GLint mTexture2DShadowUniformLocation;
        GLint mTextureCubeShadowUniformLocation;
        GLint mDepthRefUniformLocation;
    };
    
    class SamplerInStructTest : public Texture2DTest
    {
      protected:
        SamplerInStructTest() : Texture2DTest() {}
    
        const char *getTextureUniformName() override { return "us.tex"; }
    
        const char *getFragmentShaderSource() override
        {
            return "precision highp float;\n"
                   "struct S\n"
                   "{\n"
                   "    vec4 a;\n"
                   "    highp sampler2D tex;\n"
                   "};\n"
                   "uniform S us;\n"
                   "varying vec2 texcoord;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_FragColor = texture2D(us.tex, texcoord + us.a.x);\n"
                   "}\n";
        }
    
        void runSamplerInStructTest()
        {
            setUpProgram();
    
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, mTexture2D);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         &GLColor::green);
            drawQuad(mProgram, "position", 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
        }
    };
    
    class SamplerInStructAsFunctionParameterTest : public SamplerInStructTest
    {
      protected:
        SamplerInStructAsFunctionParameterTest() : SamplerInStructTest() {}
    
        const char *getFragmentShaderSource() override
        {
            return "precision highp float;\n"
                   "struct S\n"
                   "{\n"
                   "    vec4 a;\n"
                   "    highp sampler2D tex;\n"
                   "};\n"
                   "uniform S us;\n"
                   "varying vec2 texcoord;\n"
                   "vec4 sampleFrom(S s) {\n"
                   "    return texture2D(s.tex, texcoord + s.a.x);\n"
                   "}\n"
                   "void main()\n"
                   "{\n"
                   "    gl_FragColor = sampleFrom(us);\n"
                   "}\n";
        }
    };
    
    class SamplerInStructArrayAsFunctionParameterTest : public SamplerInStructTest
    {
      protected:
        SamplerInStructArrayAsFunctionParameterTest() : SamplerInStructTest() {}
    
        const char *getTextureUniformName() override { return "us[0].tex"; }
    
        const char *getFragmentShaderSource() override
        {
            return "precision highp float;\n"
                   "struct S\n"
                   "{\n"
                   "    vec4 a;\n"
                   "    highp sampler2D tex;\n"
                   "};\n"
                   "uniform S us[1];\n"
                   "varying vec2 texcoord;\n"
                   "vec4 sampleFrom(S s) {\n"
                   "    return texture2D(s.tex, texcoord + s.a.x);\n"
                   "}\n"
                   "void main()\n"
                   "{\n"
                   "    gl_FragColor = sampleFrom(us[0]);\n"
                   "}\n";
        }
    };
    
    class SamplerInNestedStructAsFunctionParameterTest : public SamplerInStructTest
    {
      protected:
        SamplerInNestedStructAsFunctionParameterTest() : SamplerInStructTest() {}
    
        const char *getTextureUniformName() override { return "us[0].sub.tex"; }
    
        const char *getFragmentShaderSource() override
        {
            return "precision highp float;\n"
                   "struct SUB\n"
                   "{\n"
                   "    vec4 a;\n"
                   "    highp sampler2D tex;\n"
                   "};\n"
                   "struct S\n"
                   "{\n"
                   "    SUB sub;\n"
                   "};\n"
                   "uniform S us[1];\n"
                   "varying vec2 texcoord;\n"
                   "vec4 sampleFrom(SUB s) {\n"
                   "    return texture2D(s.tex, texcoord + s.a.x);\n"
                   "}\n"
                   "void main()\n"
                   "{\n"
                   "    gl_FragColor = sampleFrom(us[0].sub);\n"
                   "}\n";
        }
    };
    
    class SamplerInStructAndOtherVariableTest : public SamplerInStructTest
    {
      protected:
        SamplerInStructAndOtherVariableTest() : SamplerInStructTest() {}
    
        const char *getFragmentShaderSource() override
        {
            return "precision highp float;\n"
                   "struct S\n"
                   "{\n"
                   "    vec4 a;\n"
                   "    highp sampler2D tex;\n"
                   "};\n"
                   "uniform S us;\n"
                   "uniform float us_tex;\n"
                   "varying vec2 texcoord;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_FragColor = texture2D(us.tex, texcoord + us.a.x + us_tex);\n"
                   "}\n";
        }
    };
    
    class Texture2DIntegerTestES3 : public Texture2DTest
    {
      protected:
        Texture2DIntegerTestES3() : Texture2DTest() {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "precision highp usampler2D;\n"
                   "uniform usampler2D tex;\n"
                   "in vec2 texcoord;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    fragColor = vec4(texture(tex, texcoord))/255.0;\n"
                   "}\n";
        }
    };
    
    class TextureCubeIntegerTestES3 : public TexCoordDrawTest
    {
      protected:
        TextureCubeIntegerTestES3()
            : TexCoordDrawTest(), mTextureCube(0), mTextureCubeUniformLocation(-1)
        {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = 0.5*position.xy;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "precision highp usamplerCube;\n"
                   "uniform usamplerCube texCube;\n"
                   "in vec2 texcoord;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    fragColor = vec4(texture(texCube, vec3(texcoord, 1)))/255.0;\n"
                   "}\n";
        }
    
        void testSetUp() override
        {
            TexCoordDrawTest::testSetUp();
            glGenTextures(1, &mTextureCube);
            setUpProgram();
    
            mTextureCubeUniformLocation = glGetUniformLocation(mProgram, "texCube");
            ASSERT_NE(-1, mTextureCubeUniformLocation);
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mTextureCube);
            TexCoordDrawTest::testTearDown();
        }
    
        GLuint mTextureCube;
        GLint mTextureCubeUniformLocation;
    };
    
    class TextureCubeIntegerEdgeTestES3 : public TextureCubeIntegerTestES3
    {
      protected:
        TextureCubeIntegerEdgeTestES3() : TextureCubeIntegerTestES3() {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = position.xy;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "precision highp usamplerCube;\n"
                   "uniform usamplerCube texCube;\n"
                   "in vec2 texcoord;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    fragColor = vec4(texture(texCube, vec3(texcoord, 0)))/255.0;\n"
                   "}\n";
        }
    };
    
    class Texture2DIntegerProjectiveOffsetTestES3 : public Texture2DTest
    {
      protected:
        Texture2DIntegerProjectiveOffsetTestES3() : Texture2DTest() {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = 0.5*position.xy + vec2(0.5, 0.5);\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "precision highp usampler2D;\n"
                   "uniform usampler2D tex;\n"
                   "in vec2 texcoord;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    fragColor = vec4(textureProjOffset(tex, vec3(texcoord, 1), ivec2(0,0), "
                   "0.0))/255.0;\n"
                   "}\n";
        }
    };
    
    class Texture2DArrayIntegerTestES3 : public Texture2DArrayTestES3
    {
      protected:
        Texture2DArrayIntegerTestES3() : Texture2DArrayTestES3() {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "uniform highp usampler2DArray tex2DArray;\n"
                   "in vec2 texcoord;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    fragColor = vec4(texture(tex2DArray, vec3(texcoord.x, texcoord.y, "
                   "0.0)))/255.0;\n"
                   "}\n";
        }
    };
    
    class Texture3DIntegerTestES3 : public Texture3DTestES3
    {
      protected:
        Texture3DIntegerTestES3() : Texture3DTestES3() {}
    
        const char *getVertexShaderSource() override
        {
            return "#version 300 es\n"
                   "out vec2 texcoord;\n"
                   "in vec4 position;\n"
                   "void main()\n"
                   "{\n"
                   "    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
                   "    texcoord = (position.xy * 0.5) + 0.5;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "#version 300 es\n"
                   "precision highp float;\n"
                   "uniform highp usampler3D tex3D;\n"
                   "in vec2 texcoord;\n"
                   "out vec4 fragColor;\n"
                   "void main()\n"
                   "{\n"
                   "    fragColor = vec4(texture(tex3D, vec3(texcoord, 0.0)))/255.0;\n"
                   "}\n";
        }
    };
    
    class PBOCompressedTextureTest : public Texture2DTest
    {
      protected:
        PBOCompressedTextureTest() : Texture2DTest() {}
    
        void testSetUp() override
        {
            TexCoordDrawTest::testSetUp();
            glGenTextures(1, &mTexture2D);
            glBindTexture(GL_TEXTURE_2D, mTexture2D);
            EXPECT_GL_NO_ERROR();
    
            setUpProgram();
    
            glGenBuffers(1, &mPBO);
        }
    
        void testTearDown() override
        {
            glDeleteBuffers(1, &mPBO);
            Texture2DTest::testTearDown();
        }
    
        GLuint mPBO;
    };
    
    class ETC1CompressedTextureTest : public Texture2DTest
    {
      protected:
        ETC1CompressedTextureTest() : Texture2DTest() {}
    
        void testSetUp() override
        {
            TexCoordDrawTest::testSetUp();
            glGenTextures(1, &mTexture2D);
            glBindTexture(GL_TEXTURE_2D, mTexture2D);
            EXPECT_GL_NO_ERROR();
    
            setUpProgram();
        }
    
        void testTearDown() override { Texture2DTest::testTearDown(); }
    };
    
    TEST_P(Texture2DTest, NegativeAPISubImage)
    {
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        EXPECT_GL_ERROR(GL_NO_ERROR);
    
        setUpProgram();
    
        const GLubyte *pixels[20] = {0};
        glTexSubImage2D(GL_TEXTURE_2D, 0, 1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
        EXPECT_GL_ERROR(GL_INVALID_VALUE);
    
        if (IsGLExtensionEnabled("GL_EXT_texture_storage"))
        {
            // Create a 1-level immutable texture.
            glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, 2, 2);
    
            // Try calling sub image on the second level.
            glTexSubImage2D(GL_TEXTURE_2D, 1, 1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
        }
    }
    
    // Test that querying GL_TEXTURE_BINDING* doesn't cause an unexpected error.
    TEST_P(Texture2DTest, QueryBinding)
    {
        glBindTexture(GL_TEXTURE_2D, 0);
        EXPECT_GL_ERROR(GL_NO_ERROR);
    
        GLint textureBinding;
        glGetIntegerv(GL_TEXTURE_BINDING_2D, &textureBinding);
        EXPECT_GL_NO_ERROR();
        EXPECT_EQ(0, textureBinding);
    
        glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &textureBinding);
        if (IsGLExtensionEnabled("GL_OES_EGL_image_external") ||
            IsGLExtensionEnabled("GL_NV_EGL_stream_consumer_external"))
        {
            EXPECT_GL_NO_ERROR();
            EXPECT_EQ(0, textureBinding);
        }
        else
        {
            EXPECT_GL_ERROR(GL_INVALID_ENUM);
        }
    }
    
    TEST_P(Texture2DTest, ZeroSizedUploads)
    {
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        EXPECT_GL_ERROR(GL_NO_ERROR);
    
        setUpProgram();
    
        // Use the texture first to make sure it's in video memory
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
    
        const GLubyte *pixel[4] = {0};
    
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
        EXPECT_GL_NO_ERROR();
    
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
        EXPECT_GL_NO_ERROR();
    
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
        EXPECT_GL_NO_ERROR();
    }
    
    TEST_P(Texture2DTest, DefineMultipleLevelsWithoutMipmapping)
    {
        setUpProgram();
    
        constexpr size_t kImageSize = 256;
        std::array<GLColor, kImageSize * kImageSize> kMipColors[2];
    
        std::fill(kMipColors[0].begin(), kMipColors[0].end(), GLColor::red);
        std::fill(kMipColors[1].begin(), kMipColors[1].end(), GLColor::green);
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kImageSize, kImageSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kMipColors[0].data());
        EXPECT_GL_NO_ERROR();
    
        // Draw so the image is created.
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
    
        // Define level 1 of the texture.
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, kImageSize, kImageSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kMipColors[1].data());
        EXPECT_GL_NO_ERROR();
    
        // Draw again.
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[0][0]);
    }
    
    // Test drawing with two texture types, to trigger an ANGLE bug in validation
    TEST_P(TextureCubeTest, CubeMapBug)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
        EXPECT_GL_ERROR(GL_NO_ERROR);
    
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        glUniform1i(mTextureCubeUniformLocation, 1);
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
    }
    
    // Test drawing with two texture types accessed from the same shader and check that the result of
    // drawing is correct.
    TEST_P(TextureCubeTest, CubeMapDraw)
    {
        GLubyte texData[4];
        texData[0] = 0;
        texData[1] = 60;
        texData[2] = 0;
        texData[3] = 255;
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
        texData[1] = 120;
        glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        texData);
        EXPECT_GL_ERROR(GL_NO_ERROR);
    
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        glUniform1i(mTextureCubeUniformLocation, 1);
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
    
        int px = getWindowWidth() - 1;
        int py = 0;
        EXPECT_PIXEL_NEAR(px, py, 0, 180, 0, 255, 2);
    }
    
    TEST_P(Sampler2DAsFunctionParameterTest, Sampler2DAsFunctionParameter)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        GLubyte texData[4];
        texData[0] = 0;
        texData[1] = 128;
        texData[2] = 0;
        texData[3] = 255;
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, texData);
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
    
        EXPECT_PIXEL_NEAR(0, 0, 0, 128, 0, 255, 2);
    }
    
    // Test drawing with two textures passed to the shader in a sampler array.
    TEST_P(SamplerArrayTest, SamplerArrayDraw)
    {
        testSamplerArrayDraw();
    }
    
    // Test drawing with two textures passed to the shader in a sampler array which is passed to a
    // user-defined function in the shader.
    TEST_P(SamplerArrayAsFunctionParameterTest, SamplerArrayAsFunctionParameter)
    {
        // TODO: Diagnose and fix. http://anglebug.com/2955
        ANGLE_SKIP_TEST_IF(IsVulkan() && IsAndroid());
    
        testSamplerArrayDraw();
    }
    
    // Copy of a test in conformance/textures/texture-mips, to test generate mipmaps
    TEST_P(Texture2DTestWithDrawScale, MipmapsTwice)
    {
        int px = getWindowWidth() / 2;
        int py = getWindowHeight() / 2;
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        std::vector<GLColor> pixelsRed(16u * 16u, GLColor::red);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixelsRed.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glGenerateMipmap(GL_TEXTURE_2D);
    
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        glUniform2f(mDrawScaleUniformLocation, 0.0625f, 0.0625f);
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
    
        std::vector<GLColor> pixelsBlue(16u * 16u, GLColor::blue);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     pixelsBlue.data());
        glGenerateMipmap(GL_TEXTURE_2D);
    
        std::vector<GLColor> pixelsGreen(16u * 16u, GLColor::green);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     pixelsGreen.data());
        glGenerateMipmap(GL_TEXTURE_2D);
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
    }
    
    // Test creating a FBO with a cube map render target, to test an ANGLE bug
    // https://code.google.com/p/angleproject/issues/detail?id=849
    TEST_P(TextureCubeTest, CubeMapFBO)
    {
        // http://anglebug.com/3145
        ANGLE_SKIP_TEST_IF(IsFuchsia() && IsIntel() && IsVulkan());
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    
        glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
                               mTextureCube, 0);
    
        EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
        EXPECT_GL_NO_ERROR();
    
        // Test clearing the six mip faces individually.
        std::array<GLColor, 6> faceColors = {{GLColor::red, GLColor::green, GLColor::blue,
                                              GLColor::yellow, GLColor::cyan, GLColor::magenta}};
    
        for (size_t faceIndex = 0; faceIndex < 6; ++faceIndex)
        {
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                   GL_TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, mTextureCube, 0);
    
            Vector4 clearColorF = faceColors[faceIndex].toNormalizedVector();
            glClearColor(clearColorF.x(), clearColorF.y(), clearColorF.z(), clearColorF.w());
            glClear(GL_COLOR_BUFFER_BIT);
    
            EXPECT_PIXEL_COLOR_EQ(0, 0, faceColors[faceIndex]);
        }
    
        // Iterate the faces again to make sure the colors haven't changed.
        for (size_t faceIndex = 0; faceIndex < 6; ++faceIndex)
        {
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                                   GL_TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, mTextureCube, 0);
            EXPECT_PIXEL_COLOR_EQ(0, 0, faceColors[faceIndex])
                << "face color " << faceIndex << " shouldn't change";
        }
    }
    
    // Tests clearing a cube map with a scissor enabled.
    TEST_P(TextureCubeTest, CubeMapFBOScissoredClear)
    {
        // http://anglebug.com/3145
        ANGLE_SKIP_TEST_IF(IsFuchsia() && IsIntel() && IsVulkan());
    
        constexpr size_t kSize = 16;
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glViewport(0, 0, kSize, kSize);
    
        GLTexture texcube;
        glBindTexture(GL_TEXTURE_CUBE_MAP, texcube);
        for (GLenum face = 0; face < 6; face++)
        {
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA,
                         GL_UNSIGNED_BYTE, nullptr);
        }
        ASSERT_GL_NO_ERROR();
    
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
                               texcube, 0);
    
        EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
        ASSERT_GL_NO_ERROR();
    
        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    
        glEnable(GL_SCISSOR_TEST);
        glScissor(kSize / 2, 0, kSize / 2, kSize);
        glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(kSize / 2 + 1, 0, GLColor::green);
    
        ASSERT_GL_NO_ERROR();
    }
    
    // Test that glTexSubImage2D works properly when glTexStorage2DEXT has initialized the image with a
    // default color.
    TEST_P(Texture2DTest, TexStorage)
    {
        ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 &&
                           !IsGLExtensionEnabled("GL_EXT_texture_storage"));
    
        int width  = getWindowWidth();
        int height = getWindowHeight();
    
        GLuint tex2D;
        glGenTextures(1, &tex2D);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex2D);
    
        // Fill with red
        std::vector<GLubyte> pixels(3 * 16 * 16);
        for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
        {
            pixels[pixelId * 3 + 0] = 255;
            pixels[pixelId * 3 + 1] = 0;
            pixels[pixelId * 3 + 2] = 0;
        }
    
        // ANGLE internally uses RGBA as the DirectX format for RGB images
        // therefore glTexStorage2DEXT initializes the image to a default color to get a consistent
        // alpha color. The data is kept in a CPU-side image and the image is marked as dirty.
        if (getClientMajorVersion() >= 3)
        {
            glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB8, 16, 16);
        }
        else
        {
            glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGB8, 16, 16);
        }
    
        // Initializes the color of the upper-left 8x8 pixels, leaves the other pixels untouched.
        // glTexSubImage2D should take into account that the image is dirty.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
        setUpProgram();
    
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
        glDeleteTextures(1, &tex2D);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
    
        // Validate that the region of the texture without data has an alpha of 1.0
        angle::GLColor pixel = ReadColor(3 * width / 4, 3 * height / 4);
        EXPECT_EQ(255, pixel.A);
    }
    
    // Test that glTexSubImage2D combined with a PBO works properly when glTexStorage2DEXT has
    // initialized the image with a default color.
    TEST_P(Texture2DTest, TexStorageWithPBO)
    {
        // http://anglebug.com/4126
        ANGLE_SKIP_TEST_IF(IsOSX() && IsOpenGL());
    
        // http://anglebug.com/5081
        ANGLE_SKIP_TEST_IF(IsWindows() && IsNVIDIA() && IsOpenGL());
    
        // http://anglebug.com/5651
        ANGLE_SKIP_TEST_IF(IsLinux() && IsNVIDIA() && IsOpenGL());
    
        // http://anglebug.com/5097
        ANGLE_SKIP_TEST_IF(IsLinux() && IsOpenGL() && IsTSan());
    
        if (getClientMajorVersion() < 3)
        {
            ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
            ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
        }
    
        const int width          = getWindowWidth();
        const int height         = getWindowHeight();
        const size_t pixelCount  = width * height;
        const int componentCount = 3;
    
        GLuint tex2D;
        glGenTextures(1, &tex2D);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex2D);
    
        // Fill with red
        std::vector<GLubyte> pixels(componentCount * pixelCount);
        for (size_t pixelId = 0; pixelId < pixelCount; ++pixelId)
        {
            pixels[pixelId * componentCount + 0] = 255;
            pixels[pixelId * componentCount + 1] = 0;
            pixels[pixelId * componentCount + 2] = 0;
        }
    
        // Read 16x16 region from red backbuffer to PBO
        GLuint pbo;
        glGenBuffers(1, &pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, componentCount * pixelCount, pixels.data(),
                     GL_STATIC_DRAW);
    
        // ANGLE internally uses RGBA as the DirectX format for RGB images
        // therefore glTexStorage2DEXT initializes the image to a default color to get a consistent
        // alpha color. The data is kept in a CPU-side image and the image is marked as dirty.
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGB8, width, height);
    
        // Initializes the color of the upper-left quadrant of pixels, leaves the other pixels
        // untouched. glTexSubImage2D should take into account that the image is dirty.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width / 2, height / 2, GL_RGB, GL_UNSIGNED_BYTE,
                        nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
        setUpProgram();
    
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
        glDeleteTextures(1, &tex2D);
        glDeleteBuffers(1, &pbo);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_EQ(3 * width / 4, 3 * height / 4, 0, 0, 0, 255);
        EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
    }
    
    // Test that glTexSubImage2D combined with a PBO works properly after deleting the PBO
    // and drawing with the texture
    // Pseudo code for the follow test:
    // 1. Upload PBO to mTexture2D
    // 2. Delete PBO
    // 3. Draw with otherTexture (x5)
    // 4. Draw with mTexture2D
    // 5. Validate color output
    TEST_P(Texture2DTest, PBOWithMultipleDraws)
    {
        if (getClientMajorVersion() < 3)
        {
            ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
            ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
        }
    
        const GLuint width            = getWindowWidth();
        const GLuint height           = getWindowHeight();
        const GLuint windowPixelCount = width * height;
        std::vector<GLColor> pixelsRed(windowPixelCount, GLColor::red);
        std::vector<GLColor> pixelsGreen(windowPixelCount, GLColor::green);
    
        // Create secondary draw that does not use mTexture
        const char *vertexShaderSource   = getVertexShaderSource();
        const char *fragmentShaderSource = getFragmentShaderSource();
        ANGLE_GL_PROGRAM(otherProgram, vertexShaderSource, fragmentShaderSource);
    
        GLint uniformLoc = glGetUniformLocation(otherProgram, getTextureUniformName());
        ASSERT_NE(-1, uniformLoc);
        glUseProgram(0);
    
        // Create secondary Texture to draw with
        GLTexture otherTexture;
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, otherTexture);
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
                        pixelsRed.data());
        ASSERT_GL_NO_ERROR();
    
        // Setup primary Texture
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
        ASSERT_GL_NO_ERROR();
    
        // Setup PBO
        GLuint pbo = 0;
        glGenBuffers(1, &pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, pixelsGreen.size() * 4u, pixelsGreen.data(),
                     GL_STATIC_DRAW);
        ASSERT_GL_NO_ERROR();
    
        // Write PBO to mTexture
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        ASSERT_GL_NO_ERROR();
        // Delete PBO as ANGLE should be properly handling refcount of this buffer
        glDeleteBuffers(1, &pbo);
        pixelsGreen.clear();
    
        // Do 5 draws not involving primary texture that the PBO updated
        glUseProgram(otherProgram);
        glUniform1i(uniformLoc, 0);
        glBindTexture(GL_TEXTURE_2D, otherTexture);
        drawQuad(otherProgram, "position", 0.5f);
        glBindTexture(GL_TEXTURE_2D, 0);
        glUseProgram(0);
    
        glUseProgram(otherProgram);
        glUniform1i(uniformLoc, 0);
        glBindTexture(GL_TEXTURE_2D, otherTexture);
        drawQuad(otherProgram, "position", 0.5f);
        glBindTexture(GL_TEXTURE_2D, 0);
        glUseProgram(0);
    
        glUseProgram(otherProgram);
        glUniform1i(uniformLoc, 0);
        glBindTexture(GL_TEXTURE_2D, otherTexture);
        drawQuad(otherProgram, "position", 0.5f);
        glBindTexture(GL_TEXTURE_2D, 0);
        glUseProgram(0);
    
        glUseProgram(otherProgram);
        glUniform1i(uniformLoc, 0);
        glBindTexture(GL_TEXTURE_2D, otherTexture);
        drawQuad(otherProgram, "position", 0.5f);
        glBindTexture(GL_TEXTURE_2D, 0);
        glUseProgram(0);
        ASSERT_GL_NO_ERROR();
    
        std::vector<GLColor> output(windowPixelCount, GLColor::black);
        glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                     output.data());
        EXPECT_EQ(pixelsRed, output);
    
        setUpProgram();
        // Draw using PBO updated texture
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
    
        std::vector<GLColor> actual(windowPixelCount, GLColor::black);
        glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                     actual.data());
        // Value should be green as it was updated during PBO transfer to mTexture
        std::vector<GLColor> expected(windowPixelCount, GLColor::green);
        EXPECT_EQ(expected, actual);
    }
    
    // Test that glTexSubImage2D combined with a PBO works properly. PBO has all pixels as red
    // except the middle one being green.
    TEST_P(Texture2DTest, TexStorageWithPBOMiddlePixelDifferent)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
    
        int width  = getWindowWidth();
        int height = getWindowHeight();
    
        GLuint tex2D;
        glGenTextures(1, &tex2D);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex2D);
    
        std::vector<GLubyte> pixels(3 * 16 * 16);
    
        // Initialize texture with default black color.
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGB8, 16, 16);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 16, 16, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
    
        // Fill PBO's data with red, with middle one as green
        for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
        {
            if (pixelId == 8 * 7 + 7)
            {
                pixels[pixelId * 3 + 0] = 0;
                pixels[pixelId * 3 + 1] = 255;
                pixels[pixelId * 3 + 2] = 0;
            }
            else
            {
                pixels[pixelId * 3 + 0] = 255;
                pixels[pixelId * 3 + 1] = 0;
                pixels[pixelId * 3 + 2] = 0;
            }
        }
    
        GLuint pbo;
        glGenBuffers(1, &pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, 3 * 16 * 16, pixels.data(), GL_STATIC_DRAW);
    
        // Update the color of the texture's upper-left 8x8 pixels, leaves the other pixels untouched.
        // glTexSubImage2D should take into account that the image is dirty.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        setUpProgram();
    
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
        glDeleteTextures(1, &tex2D);
        glDeleteBuffers(1, &pbo);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_EQ(3 * width / 4, 3 * height / 4, 0, 0, 0, 255);
        EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
        EXPECT_PIXEL_EQ(width / 2 - 1, height / 2 - 1, 0, 255, 0, 255);
    }
    
    // Test that glTexSubImage2D combined with a PBO works properly when glTexImage2D has
    // initialized the image with a luminance color
    TEST_P(Texture2DTest, TexImageWithLuminancePBO)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
    
        int width  = getWindowWidth();
        int height = getWindowHeight();
    
        GLuint tex2D;
        glGenTextures(1, &tex2D);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex2D);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 16, 16, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
                     nullptr);
    
        // Fill PBO with white, with middle one as grey
        std::vector<GLubyte> pixels(16 * 16);
        for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
        {
            if (pixelId == 8 * 7 + 7)
            {
                pixels[pixelId] = 128;
            }
            else
            {
                pixels[pixelId] = 255;
            }
        }
    
        GLuint pbo;
        glGenBuffers(1, &pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, 16 * 16, pixels.data(), GL_STATIC_DRAW);
    
        // Initializes the color of the upper-left 8x8 pixels, leaves the other pixels untouched.
        // glTexSubImage2D should take into account that the image is dirty.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        setUpProgram();
    
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
        glDeleteTextures(1, &tex2D);
        glDeleteBuffers(1, &pbo);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 255, 255, 255);
        EXPECT_PIXEL_NEAR(width / 2 - 1, height / 2 - 1, 128, 128, 128, 255, 1);
    }
    
    // Test that glTexSubImage2D combined with a PBO works properly when glTexStorage2DEXT has
    // initialized the image with a RGB656 color
    TEST_P(Texture2DTest, TexImageWithRGB565PBO)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
    
        int width  = getWindowWidth();
        int height = getWindowHeight();
    
        GLuint tex2D;
        glGenTextures(1, &tex2D);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex2D);
    
        // Fill PBO with red, with middle one as green
        std::vector<GLushort> pixels(16 * 16);
        for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
        {
            if (pixelId == 8 * 7 + 8)
            {
                pixels[pixelId] = 0x7E0;
            }
            else
            {
                pixels[pixelId] = 0xF800;
            }
        }
    
        GLuint pbo;
        glGenBuffers(1, &pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, 2 * 16 * 16, pixels.data(), GL_STATIC_DRAW);
    
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGB565, 16, 16);
    
        // Initializes the color of the upper-left 8x8 pixels, leaves the other pixels untouched.
        // glTexSubImage2D should take into account that the image is dirty.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
                        reinterpret_cast<void *>(2));
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        setUpProgram();
    
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
        glDeleteTextures(1, &tex2D);
        glDeleteBuffers(1, &pbo);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
        EXPECT_PIXEL_EQ(width / 2 - 1, height / 2 - 1, 0, 255, 0, 255);
    }
    
    // Test that glTexSubImage2D combined with a PBO works properly when glTexStorage2DEXT has
    // initialized the image with a RGBA4444 color
    TEST_P(Texture2DTest, TexImageWithRGBA4444PBO)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
    
        int width  = getWindowWidth();
        int height = getWindowHeight();
    
        GLuint tex2D;
        glGenTextures(1, &tex2D);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex2D);
    
        // Fill PBO with red, with middle one as green
        std::vector<GLushort> pixels(16 * 16);
        for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
        {
            if (pixelId == 8 * 7 + 8)
            {
                pixels[pixelId] = 0xF0F;
            }
            else
            {
                pixels[pixelId] = 0xF00F;
            }
        }
    
        GLuint pbo;
        glGenBuffers(1, &pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, 2 * 16 * 16, pixels.data(), GL_STATIC_DRAW);
    
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA4, 16, 16);
    
        // Initializes the color of the upper-left 8x8 pixels, leaves the other pixels untouched.
        // glTexSubImage2D should take into account that the image is dirty.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4,
                        reinterpret_cast<void *>(2));
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        setUpProgram();
    
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
        glDeleteTextures(1, &tex2D);
        glDeleteBuffers(1, &pbo);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
        EXPECT_PIXEL_EQ(width / 2 - 1, height / 2 - 1, 0, 255, 0, 255);
    }
    
    // Test that glTexSubImage2D combined with a PBO works properly when glTexStorage2DEXT has
    // initialized the image with a RGBA5551 color
    TEST_P(Texture2DTest, TexImageWithRGBA5551PBO)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
    
        int width  = getWindowWidth();
        int height = getWindowHeight();
    
        GLuint tex2D;
        glGenTextures(1, &tex2D);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex2D);
    
        // Fill PBO with red, with middle one as green
        std::vector<GLushort> pixels(16 * 16);
        for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
        {
            if (pixelId == 8 * 7 + 7)
            {
                pixels[pixelId] = 0x7C1;
            }
            else
            {
                pixels[pixelId] = 0xF801;
            }
        }
    
        GLuint pbo;
        glGenBuffers(1, &pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, 2 * 16 * 16, pixels.data(), GL_STATIC_DRAW);
    
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGB5_A1, 16, 16);
    
        // Initializes the color of the upper-left 8x8 pixels, leaves the other pixels untouched.
        // glTexSubImage2D should take into account that the image is dirty.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        setUpProgram();
    
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
        glDeleteTextures(1, &tex2D);
        glDeleteBuffers(1, &pbo);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
        EXPECT_PIXEL_EQ(width / 2 - 1, height / 2 - 1, 0, 255, 0, 255);
    }
    
    // Test that glTexSubImage2D combined with a PBO works properly when glTexStorage2D has
    // initialized the image with a depth-only format.
    TEST_P(Texture2DTestES3, TexImageWithDepthPBO)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
    
        // http://anglebug.com/5315
        ANGLE_SKIP_TEST_IF(IsOpenGL() && IsOSX());
    
        // http://anglebug.com/5316
        ANGLE_SKIP_TEST_IF(IsMetal() && IsOSX());
    
        constexpr GLsizei kSize = 4;
    
        // Set up the framebuffer.
        GLTexture colorTexture;
        glBindTexture(GL_TEXTURE_2D, colorTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        ASSERT_GL_NO_ERROR();
    
        GLTexture depthTexture;
        glBindTexture(GL_TEXTURE_2D, depthTexture);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH_COMPONENT16, kSize, kSize);
        ASSERT_GL_NO_ERROR();
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        ASSERT_GL_NO_ERROR();
    
        // Clear depth to 0, ensuring the texture's image is allocated.
        glClearDepthf(0);
        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
    
        // Fill depth with 1.0f.
        std::vector<GLushort> pixels(kSize * kSize);
        for (size_t pixelId = 0; pixelId < pixels.size(); ++pixelId)
        {
            pixels[pixelId] = 0xFFFF;
        }
    
        GLuint pbo;
        glGenBuffers(1, &pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, pixels.size() * sizeof(pixels[0]), pixels.data(),
                     GL_STATIC_DRAW);
        ASSERT_GL_NO_ERROR();
    
        // Upload PBO data.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSize, kSize, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT,
                        nullptr);
    
        // If depth is not set to 1, rendering would fail.
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LESS);
    
        // Draw red
        ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.95f);
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_RECT_EQ(0, 0, kSize, kSize, GLColor::red);
    }
    
    // Test that glTexSubImage2D combined with a PBO works properly when glTexStorage2D has
    // initialized the image with a stencil-only format.
    TEST_P(Texture2DTestES3, TexImageWithStencilPBO)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
    
        // http://anglebug.com/5315
        ANGLE_SKIP_TEST_IF(IsOpenGL() && IsOSX());
    
        // http://anglebug.com/5316
        ANGLE_SKIP_TEST_IF(IsMetal() && IsOSX());
    
        // http://anglebug.com/5317
        ANGLE_SKIP_TEST_IF(IsWindows() && IsD3D());
    
        constexpr GLsizei kSize = 4;
    
        // Set up the framebuffer.
        GLTexture colorTexture;
        glBindTexture(GL_TEXTURE_2D, colorTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        ASSERT_GL_NO_ERROR();
    
        GLTexture stencilTexture;
        glBindTexture(GL_TEXTURE_2D, stencilTexture);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_STENCIL_INDEX8, kSize, kSize);
        ASSERT_GL_NO_ERROR();
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, stencilTexture, 0);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        ASSERT_GL_NO_ERROR();
    
        // Clear stencil to 0, ensuring the texture's image is allocated.
        glClearStencil(0);
        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
    
        // Fill stencil with 0x4E
        std::vector<GLubyte> pixels(kSize * kSize);
        for (size_t pixelId = 0; pixelId < pixels.size(); ++pixelId)
        {
            pixels[pixelId] = 0x4E;
        }
    
        GLuint pbo;
        glGenBuffers(1, &pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, pixels.size() * sizeof(pixels[0]), pixels.data(),
                     GL_STATIC_DRAW);
        ASSERT_GL_NO_ERROR();
    
        // Upload PBO data.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSize, kSize, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE,
                        nullptr);
        ASSERT_GL_NO_ERROR();
    
        // If stencil is not set to 0x4E, rendering would fail.
        glEnable(GL_STENCIL_TEST);
        glStencilFunc(GL_EQUAL, 0x4E, 0xFF);
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
        glStencilMask(0xFF);
    
        // Draw red
        ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.95f);
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_RECT_EQ(0, 0, kSize, kSize, GLColor::red);
    }
    
    // Test that glTexSubImage2D combined with a PBO works properly when glTexStorage2D has
    // initialized the image with a depth/stencil format.
    TEST_P(Texture2DTestES3, TexImageWithDepthStencilPBO)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
    
        // http://anglebug.com/5313
        ANGLE_SKIP_TEST_IF(IsVulkan() && IsAndroid());
    
        // http://anglebug.com/5315
        ANGLE_SKIP_TEST_IF(IsOpenGL() && IsOSX());
    
        // http://anglebug.com/5316
        ANGLE_SKIP_TEST_IF(IsMetal() && IsOSX());
    
        // http://anglebug.com/5317
        ANGLE_SKIP_TEST_IF(IsWindows() && IsD3D());
    
        constexpr GLsizei kSize = 4;
    
        // Set up the framebuffer.
        GLTexture colorTexture;
        glBindTexture(GL_TEXTURE_2D, colorTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        ASSERT_GL_NO_ERROR();
    
        GLTexture depthStencilTexture;
        glBindTexture(GL_TEXTURE_2D, depthStencilTexture);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_DEPTH24_STENCIL8, kSize, kSize);
        ASSERT_GL_NO_ERROR();
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
                               depthStencilTexture, 0);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        ASSERT_GL_NO_ERROR();
    
        // Clear depth and stencil to 0, ensuring the texture's image is allocated.
        glClearDepthf(0);
        glClearStencil(0);
        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
    
        // Fill depth with 1.0f and stencil with 0xD5
        std::vector<GLuint> pixels(kSize * kSize);
        for (size_t pixelId = 0; pixelId < pixels.size(); ++pixelId)
        {
            pixels[pixelId] = 0xFFFFFFD5;
        }
    
        GLuint pbo;
        glGenBuffers(1, &pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, pixels.size() * sizeof(pixels[0]), pixels.data(),
                     GL_STATIC_DRAW);
        ASSERT_GL_NO_ERROR();
    
        // Upload PBO data.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSize, kSize, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8,
                        nullptr);
    
        // If depth is not set to 1, rendering would fail.
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LESS);
    
        // If stencil is not set to 0xD5, rendering would fail.
        glEnable(GL_STENCIL_TEST);
        glStencilFunc(GL_EQUAL, 0xD5, 0xFF);
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
        glStencilMask(0xFF);
    
        // Draw red
        ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.95f);
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_RECT_EQ(0, 0, kSize, kSize, GLColor::red);
    }
    
    // Tests CopySubImage for float formats
    TEST_P(Texture2DTest, CopySubImageFloat_R_R)
    {
        testFloatCopySubImage(1, 1);
    }
    
    TEST_P(Texture2DTest, CopySubImageFloat_RG_R)
    {
        testFloatCopySubImage(2, 1);
    }
    
    TEST_P(Texture2DTest, CopySubImageFloat_RG_RG)
    {
        testFloatCopySubImage(2, 2);
    }
    
    TEST_P(Texture2DTest, CopySubImageFloat_RGB_R)
    {
        testFloatCopySubImage(3, 1);
    }
    
    TEST_P(Texture2DTest, CopySubImageFloat_RGB_RG)
    {
        testFloatCopySubImage(3, 2);
    }
    
    TEST_P(Texture2DTest, CopySubImageFloat_RGB_RGB)
    {
        // TODO(cwallez): Fix on Linux Intel drivers (http://anglebug.com/1346)
        ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux() && IsOpenGL());
    
        // Ignore SDK layers messages on D3D11 FL 9.3 (http://anglebug.com/1284)
        ANGLE_SKIP_TEST_IF(IsD3D11_FL93());
    
        testFloatCopySubImage(3, 3);
    }
    
    TEST_P(Texture2DTest, CopySubImageFloat_RGBA_R)
    {
        testFloatCopySubImage(4, 1);
    }
    
    TEST_P(Texture2DTest, CopySubImageFloat_RGBA_RG)
    {
        testFloatCopySubImage(4, 2);
    }
    
    TEST_P(Texture2DTest, CopySubImageFloat_RGBA_RGB)
    {
        // Ignore SDK layers messages on D3D11 FL 9.3 (http://anglebug.com/1284)
        ANGLE_SKIP_TEST_IF(IsD3D11_FL93());
    
        testFloatCopySubImage(4, 3);
    }
    
    TEST_P(Texture2DTest, CopySubImageFloat_RGBA_RGBA)
    {
        // Ignore SDK layers messages on D3D11 FL 9.3 (http://anglebug.com/1284)
        ANGLE_SKIP_TEST_IF(IsD3D11_FL93());
    
        testFloatCopySubImage(4, 4);
    }
    
    // Port of
    // https://www.khronos.org/registry/webgl/conformance-suites/1.0.3/conformance/textures/texture-npot.html
    // Run against GL_ALPHA/UNSIGNED_BYTE format, to ensure that D3D11 Feature Level 9_3 correctly
    // handles GL_ALPHA
    TEST_P(Texture2DTest, TextureNPOT_GL_ALPHA_UBYTE)
    {
        const int npotTexSize = 5;
        const int potTexSize  = 4;  // Should be less than npotTexSize
        GLuint tex2D;
    
        if (IsGLExtensionEnabled("GL_OES_texture_npot"))
        {
            // This test isn't applicable if texture_npot is enabled
            return;
        }
    
        setUpProgram();
    
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    
        // Default unpack alignment is 4. The values of 'pixels' below needs it to be 1.
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    
        glActiveTexture(GL_TEXTURE0);
        glGenTextures(1, &tex2D);
        glBindTexture(GL_TEXTURE_2D, tex2D);
    
        const std::vector<GLubyte> pixels(1 * npotTexSize * npotTexSize, 64);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
        // Check that an NPOT texture not on level 0 generates INVALID_VALUE
        glTexImage2D(GL_TEXTURE_2D, 1, GL_ALPHA, npotTexSize, npotTexSize, 0, GL_ALPHA,
                     GL_UNSIGNED_BYTE, pixels.data());
        EXPECT_GL_ERROR(GL_INVALID_VALUE);
    
        // Check that an NPOT texture on level 0 succeeds
        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, npotTexSize, npotTexSize, 0, GL_ALPHA,
                     GL_UNSIGNED_BYTE, pixels.data());
        EXPECT_GL_NO_ERROR();
    
        // Check that generateMipmap fails on NPOT
        glGenerateMipmap(GL_TEXTURE_2D);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    
        // Check that nothing is drawn if filtering is not correct for NPOT
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glClear(GL_COLOR_BUFFER_BIT);
        drawQuad(mProgram, "position", 1.0f);
        EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 0, 0, 255);
    
        // NPOT texture with TEXTURE_MIN_FILTER not NEAREST or LINEAR should draw with 0,0,0,255
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
        glClear(GL_COLOR_BUFFER_BIT);
        drawQuad(mProgram, "position", 1.0f);
        EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 0, 0, 255);
    
        // NPOT texture with TEXTURE_MIN_FILTER set to LINEAR should draw
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glClear(GL_COLOR_BUFFER_BIT);
        drawQuad(mProgram, "position", 1.0f);
        EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 0, 0, 64);
    
        // Check that glTexImage2D for POT texture succeeds
        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, potTexSize, potTexSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE,
                     pixels.data());
        EXPECT_GL_NO_ERROR();
    
        // Check that generateMipmap for an POT texture succeeds
        glGenerateMipmap(GL_TEXTURE_2D);
        EXPECT_GL_NO_ERROR();
    
        // POT texture with TEXTURE_MIN_FILTER set to LINEAR_MIPMAP_LINEAR should draw
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glClear(GL_COLOR_BUFFER_BIT);
        drawQuad(mProgram, "position", 1.0f);
        EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 0, 0, 64);
        EXPECT_GL_NO_ERROR();
    }
    
    // Test to ensure that glTexSubImage2D always accepts data for non-power-of-two subregions.
    // ANGLE previously rejected this if GL_OES_texture_npot wasn't active, which is incorrect.
    TEST_P(Texture2DTest, NPOTSubImageParameters)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        // Create an 8x8 (i.e. power-of-two) texture.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glGenerateMipmap(GL_TEXTURE_2D);
    
        // Supply a 3x3 (i.e. non-power-of-two) subimage to the texture.
        // This should always work, even if GL_OES_texture_npot isn't active.
        std::array<GLColor, 3 * 3> data;
        glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, 3, 3, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
    
        EXPECT_GL_NO_ERROR();
    }
    
    // Test that drawing works correctly RGBA 3D texture
    TEST_P(Texture3DTestES2, RGBA)
    {
        ANGLE_SKIP_TEST_IF(!hasTexture3DExt());
    
        // http://anglebug.com/5728
        ANGLE_SKIP_TEST_IF(IsOzone());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, mTexture3D);
        std::vector<GLColor> texDataGreen(2u * 2u * 2u, GLColor::green);
        std::vector<GLColor> texDataRed(1u * 1u * 1u, GLColor::red);
        glTexImage3DOES(GL_TEXTURE_3D, 0, GL_RGBA, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                        texDataGreen.data());
        glTexImage3DOES(GL_TEXTURE_3D, 1, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                        texDataRed.data());
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Test that drawing works correctly Luminance 3D texture
    TEST_P(Texture3DTestES2, Luminance)
    {
        ANGLE_SKIP_TEST_IF(!hasTexture3DExt());
    
        // http://anglebug.com/5728
        ANGLE_SKIP_TEST_IF(IsOzone());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, mTexture3D);
        std::vector<GLubyte> texData(2u * 2u * 2u, 125);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTexImage3DOES(GL_TEXTURE_3D, 0, GL_LUMINANCE, 2, 2, 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
                        texData.data());
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(125, 125, 125, 255));
    }
    
    // Test that drawing works correctly with glCopyTexSubImage3D
    TEST_P(Texture3DTestES2, CopySubImageRGBA)
    {
        ANGLE_SKIP_TEST_IF(!hasTexture3DExt());
    
        // http://anglebug.com/5728
        ANGLE_SKIP_TEST_IF(IsOzone());
    
        glClearColor(0, 0, 1, 1);
        glClear(GL_COLOR_BUFFER_BIT);
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, mTexture3D);
        std::vector<GLColor> texDataRed(4u * 4u * 4u, GLColor::red);
        glTexImage3DOES(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                        texDataRed.data());
        glTexImage3DOES(GL_TEXTURE_3D, 1, GL_RGBA, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                        texDataRed.data());
        glTexImage3DOES(GL_TEXTURE_3D, 2, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                        texDataRed.data());
        glCopyTexSubImage3DOES(GL_TEXTURE_3D, 1, 0, 0, 0, 0, 0, 2, 2);
        glCopyTexSubImage3DOES(GL_TEXTURE_3D, 1, 0, 0, 1, 0, 0, 2, 2);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    
        EXPECT_GL_NO_ERROR();
    
        glClearColor(0, 1, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        glUseProgram(mProgram);
        glUniform1f(glGetUniformLocation(mProgram, "level"), 1);
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    }
    
    TEST_P(Texture3DTestES2, CopySubImageLuminance)
    {
        ANGLE_SKIP_TEST_IF(!hasTexture3DExt());
    
        // http://anglebug.com/5728
        ANGLE_SKIP_TEST_IF(IsOzone());
    
        glClearColor(1, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, mTexture3D);
        glTexImage3DOES(GL_TEXTURE_3D, 0, GL_LUMINANCE, 4, 4, 4, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
                        nullptr);
        glTexImage3DOES(GL_TEXTURE_3D, 1, GL_LUMINANCE, 2, 2, 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
                        nullptr);
        glTexImage3DOES(GL_TEXTURE_3D, 2, GL_LUMINANCE, 1, 1, 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
                        nullptr);
        glCopyTexSubImage3DOES(GL_TEXTURE_3D, 1, 0, 0, 0, 0, 0, 2, 2);
        glCopyTexSubImage3DOES(GL_TEXTURE_3D, 1, 0, 0, 1, 0, 0, 2, 2);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    
        EXPECT_GL_NO_ERROR();
    
        glClearColor(0, 1, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        glUseProgram(mProgram);
        glUniform1f(glGetUniformLocation(mProgram, "level"), 1);
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    }
    
    TEST_P(Texture3DTestES2, CopySubImageAlpha)
    {
        ANGLE_SKIP_TEST_IF(!hasTexture3DExt());
    
        // http://anglebug.com/5728
        ANGLE_SKIP_TEST_IF(IsOzone());
    
        glClearColor(1, 0, 0, 0.5);
        glClear(GL_COLOR_BUFFER_BIT);
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, mTexture3D);
        glTexImage3DOES(GL_TEXTURE_3D, 0, GL_ALPHA, 4, 4, 4, 0, GL_ALPHA, GL_UNSIGNED_BYTE, nullptr);
        glTexImage3DOES(GL_TEXTURE_3D, 1, GL_ALPHA, 2, 2, 2, 0, GL_ALPHA, GL_UNSIGNED_BYTE, nullptr);
        glTexImage3DOES(GL_TEXTURE_3D, 2, GL_ALPHA, 1, 1, 1, 0, GL_ALPHA, GL_UNSIGNED_BYTE, nullptr);
        glCopyTexSubImage3DOES(GL_TEXTURE_3D, 1, 0, 0, 0, 0, 0, 2, 2);
        glCopyTexSubImage3DOES(GL_TEXTURE_3D, 1, 0, 0, 1, 0, 0, 2, 2);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    
        EXPECT_GL_NO_ERROR();
    
        glClearColor(0, 1, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        glUseProgram(mProgram);
        glUniform1f(glGetUniformLocation(mProgram, "level"), 1);
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(0, 0, 0, 128), 1.0);
    }
    
    // Verify shrinking a texture with glTexStorage2D works correctly
    TEST_P(Texture2DTestES3, ChangeTexSizeWithTexStorage)
    {
        // TODO: http://anglebug.com/5256
        ANGLE_SKIP_TEST_IF(IsWindows() && IsOpenGL());
    
        constexpr uint32_t kSizeLarge = 128;
        constexpr uint32_t kSizeSmall = 64;
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        // Create the texture with 'large' dimensions
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSizeLarge, kSizeLarge, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
        ASSERT_GL_NO_ERROR();
    
        GLFramebuffer destFbo;
        glBindFramebuffer(GL_FRAMEBUFFER, destFbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture2D, 0);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    
        // Draw with the new texture so it's created in the back end
        ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
        glUseProgram(blueProgram);
        drawQuad(blueProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_RECT_EQ(0, 0, kSizeLarge, kSizeLarge, GLColor::blue);
    
        // Shrink the texture
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSizeSmall, kSizeSmall);
        ASSERT_GL_NO_ERROR();
    
        // Create a source texture/FBO to blit from
        GLTexture sourceTex;
        glBindTexture(GL_TEXTURE_2D, sourceTex.get());
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSizeSmall, kSizeSmall);
        ASSERT_GL_NO_ERROR();
        GLFramebuffer sourceFbo;
        glBindFramebuffer(GL_FRAMEBUFFER, sourceFbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sourceTex, 0);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        // Fill the source texture with green
        ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
        glUseProgram(greenProgram);
        drawQuad(greenProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_RECT_EQ(0, 0, kSizeSmall, kSizeSmall, GLColor::green);
    
        // Blit the source (green) to the destination
        glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFbo);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destFbo);
        glBlitFramebuffer(0, 0, kSizeSmall, kSizeSmall, 0, 0, kSizeSmall, kSizeSmall,
                          GL_COLOR_BUFFER_BIT, GL_NEAREST);
        ASSERT_GL_NO_ERROR();
    
        // Render to the default framebuffer sampling from the blited texture and verify it's green
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
        glUseProgram(texProgram);
        drawQuad(texProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
        EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
    }
    
    // Regression test for http://crbug.com/949985 to make sure dirty bits are propagated up from
    // TextureImpl and the texture is synced before being used in a draw call.
    TEST_P(Texture2DTestES3, TextureImplPropogatesDirtyBits)
    {
        ANGLE_SKIP_TEST_IF(IsIntel() && IsOpenGL());
        // Flaky hangs on Win10 AMD RX 550 GL. http://anglebug.com/3371
        ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsOpenGL());
        // D3D Debug device reports an error. http://anglebug.com/3501
        ANGLE_SKIP_TEST_IF(IsWindows() && IsD3D11());
        // Support copy from levels outside the image range. http://anglebug.com/4733
        ANGLE_SKIP_TEST_IF(IsVulkan());
    
        // The workaround in the GL backend required to trigger this bug generates driver warning
        // messages.
        ScopedIgnorePlatformMessages ignoreMessages;
    
        setUpProgram();
        glUseProgram(mProgram);
        glActiveTexture(GL_TEXTURE0 + mTexture2DUniformLocation);
    
        GLTexture dest;
        glBindTexture(GL_TEXTURE_2D, dest);
    
        GLTexture source;
        glBindTexture(GL_TEXTURE_2D, source);
    
        // Put data in mip 0 and 1
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     GLColor::red.data());
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     GLColor::green.data());
    
        // Disable mipmapping so source is complete
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
        // Force the dirty bits to be synchronized in source
        drawQuad(mProgram, "position", 1.0f);
    
        // Copy from mip 1 of the source.  In the GL backend this internally sets the base level to mip
        // 1 and sets a dirty bit.
        glCopyTextureCHROMIUM(source, 1, GL_TEXTURE_2D, dest, 0, GL_RGBA, GL_UNSIGNED_BYTE, GL_FALSE,
                              GL_FALSE, GL_FALSE);
    
        // Draw again, assertions are generated if the texture has internal dirty bits at draw time
        drawQuad(mProgram, "position", 1.0f);
    }
    
    // This test case changes the base level of a texture that's attached to a framebuffer, clears every
    // level to green, and then samples the texture when rendering. Test is taken from
    // https://www.khronos.org/registry/webgl/sdk/tests/conformance2/rendering/framebuffer-texture-changing-base-level.html
    TEST_P(Texture2DTestES3, FramebufferTextureChangingBaselevel)
    {
        // TODO(cnorthrop): Failing on Vulkan/Windows/AMD. http://anglebug.com/3996
        ANGLE_SKIP_TEST_IF(IsVulkan() && IsWindows() && IsAMD());
    
        setUpProgram();
    
        constexpr GLint width  = 8;
        constexpr GLint height = 4;
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
        // Create all mipmap levels for the texture from level 0 to the 1x1 pixel level.
        GLint level  = 0;
        GLint levelW = width;
        GLint levelH = height;
        glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, levelW, levelH, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
        while (levelW > 1 || levelH > 1)
        {
            ++level;
            levelW = static_cast<GLint>(std::max(1.0, std::floor(width / std::pow(2, level))));
            levelH = static_cast<GLint>(std::max(1.0, std::floor(height / std::pow(2, level))));
            glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, levelW, levelH, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         nullptr);
        }
    
        // Clear each level of the texture using an FBO. Change the base level to match the level used
        // for the FBO on each iteration.
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        level  = 0;
        levelW = width;
        levelH = height;
        while (levelW > 1 || levelH > 1)
        {
            levelW = static_cast<GLint>(std::floor(width / std::pow(2, level)));
            levelH = static_cast<GLint>(std::floor(height / std::pow(2, level)));
    
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, level);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, level);
    
            EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
            EXPECT_GL_NO_ERROR();
    
            glClearColor(0, 1, 0, 1);
            glClear(GL_COLOR_BUFFER_BIT);
    
            EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
            ++level;
        }
    
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glViewport(0, 0, 16, 16);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Test that changing the base level of a texture after redefining a level outside the mip-chain
    // preserves the other mips' data.
    TEST_P(Texture2DBaseMaxTestES3, ExtendMipChainAfterRedefine)
    {
        // http://anglebug.com/4699
        ANGLE_SKIP_TEST_IF(IsOpenGL() && IsIntel() && IsOSX());
    
        // http://anglebug.com/5153
        ANGLE_SKIP_TEST_IF(IsOpenGL() && IsNVIDIA() && IsOSX());
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
    
        std::array<GLColor, getTotalMipDataSize(kMip0Size)> mipData;
        fillMipData(mipData.data(), kMip0Size, kMipColors);
    
        for (size_t mip = 1; mip < kMipCount; ++mip)
        {
            glTexImage2D(GL_TEXTURE_2D, mip, GL_RGBA8, kMip0Size >> mip, kMip0Size >> mip, 0, GL_RGBA,
                         GL_UNSIGNED_BYTE, mipData.data() + getMipDataOffset(kMip0Size, mip));
        }
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 1);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    
        // Mip 1 is green.  Verify this.
        EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[1]);
    
        // http://anglebug.com/4709
        ANGLE_SKIP_TEST_IF(IsOpenGL() && (IsIntel() || IsAMD()) && IsWindows());
    
        // Add mip 0 and rebase the mip chain.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kMip0Size, kMip0Size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     mipData.data() + getMipDataOffset(kMip0Size, 0));
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
    
        // Mip 1 should still be green.
        EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[1]);
    
        // Verify the other mips too.
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[2]);
    
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 3);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[3]);
    
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    
        // http://anglebug.com/4704
        ANGLE_SKIP_TEST_IF(IsVulkan());
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[0]);
    }
    
    // Test that changing the base level of a texture multiple times preserves the data.
    TEST_P(Texture2DBaseMaxTestES3, PingPongBaseLevel)
    {
        // http://anglebug.com/4710
        ANGLE_SKIP_TEST_IF(IsD3D());
    
        // http://anglebug.com/4711
        ANGLE_SKIP_TEST_IF(IsOpenGL() && IsAMD() && IsWindows());
    
        // http://anglebug.com/4701
        ANGLE_SKIP_TEST_IF(IsOpenGL() && IsIntel() && IsOSX());
    
        initTest();
    
        // Ping pong a few times.
        for (uint32_t tries = 0; tries < 2; ++tries)
        {
            // Rebase to different mips and verify mips.
            for (uint32_t base = 0; base < kMipCount; ++base)
            {
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, base);
                for (uint32_t lod = 0; lod < kMipCount - base; ++lod)
                {
                    setLodUniform(lod);
                    drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
                    EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[base + lod]);
                }
            }
    
            // Rebase backwards and verify mips.
            for (uint32_t base = kMipCount - 2; base > 0; --base)
            {
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, base);
                for (uint32_t lod = 0; lod < kMipCount - base; ++lod)
                {
                    setLodUniform(lod);
                    drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
                    EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[base + lod]);
                }
            }
        }
    }
    
    // Test that glTexSubImage2D after incompatibly redefining a mip level correctly applies the update
    // after the redefine data.
    TEST_P(Texture2DBaseMaxTestES3, SubImageAfterRedefine)
    {
        initTest();
    
        // Test that all mips have the expected data initially (this makes sure the texture image is
        // created already).
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[lod]);
        }
    
        // Redefine every level, followed by a glTexSubImage2D
        const GLColor kNewMipColors[kMipCount] = {
            GLColor::yellow,
            GLColor::cyan,
            GLColor(127, 0, 0, 255),
            GLColor(0, 127, 0, 255),
        };
        std::array<GLColor, getTotalMipDataSize(kMip0Size * 2)> newMipData;
        fillMipData(newMipData.data(), kMip0Size * 2, kNewMipColors);
    
        const GLColor kSubImageMipColors[kMipCount] = {
            GLColor(0, 0, 127, 255),
            GLColor(127, 127, 0, 255),
            GLColor(0, 127, 127, 255),
            GLColor(127, 0, 127, 255),
        };
        std::array<GLColor, getTotalMipDataSize(kMip0Size)> subImageMipData;
        fillMipData(subImageMipData.data(), kMip0Size, kSubImageMipColors);
    
        for (size_t mip = 0; mip < kMipCount; ++mip)
        {
            // Redefine the level.
            size_t newMipSize = (kMip0Size * 2) >> mip;
            glTexImage2D(GL_TEXTURE_2D, mip, GL_RGBA8, newMipSize, newMipSize, 0, GL_RGBA,
                         GL_UNSIGNED_BYTE, newMipData.data() + getMipDataOffset(kMip0Size * 2, mip));
    
            // Immediately follow that with a subimage update.
            glTexSubImage2D(GL_TEXTURE_2D, mip, 0, 0, kMip0Size >> mip, kMip0Size >> mip, GL_RGBA,
                            GL_UNSIGNED_BYTE,
                            subImageMipData.data() + getMipDataOffset(kMip0Size, mip));
        }
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, kMipCount - 1);
    
        // Test that the texture looks as expected.
        const int w = getWindowWidth() - 1;
        const int h = getWindowHeight() - 1;
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kSubImageMipColors[lod]);
            EXPECT_PIXEL_COLOR_EQ(w, 0, kNewMipColors[lod]);
            EXPECT_PIXEL_COLOR_EQ(0, h, kNewMipColors[lod]);
            EXPECT_PIXEL_COLOR_EQ(w, h, kNewMipColors[lod]);
        }
    }
    
    // Test that incompatibly redefining a level then redefining it back to its original size works.
    TEST_P(Texture2DBaseMaxTestES3, IncompatiblyRedefineLevelThenRevert)
    {
        initTest();
    
        // Test that all mips have the expected data initially (this makes sure the texture image is
        // created already).
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[lod]);
        }
    
        // Redefine Mip 1 to be larger.
        constexpr size_t kLargeMip1Size = getMipDataSize(kMip0Size * 2, 1);
        std::array<GLColor, kLargeMip1Size> interimMipData;
        std::fill(interimMipData.data(), interimMipData.data() + kLargeMip1Size, GLColor::yellow);
    
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kMip0Size, kMip0Size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     interimMipData.data());
    
        // Redefine Mip 1 back to its original size.
        constexpr size_t kNormalMip1Size = getMipDataSize(kMip0Size, 1);
        std::array<GLColor, kLargeMip1Size> newMipData;
        std::fill(newMipData.data(), newMipData.data() + kNormalMip1Size, GLColor::cyan);
    
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kMip0Size / 2, kMip0Size / 2, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, newMipData.data());
    
        // Verify texture colors.
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, lod == 1 ? GLColor::cyan : kMipColors[lod]);
        }
    }
    
    // Test that redefining every level of a texture to another format works.  The format uses more
    // bits per component, to ensure alignment requirements for the new format are taken into account.
    TEST_P(Texture2DBaseMaxTestES3, RedefineEveryLevelToAnotherFormat)
    {
        initTest();
    
        // Test that all mips have the expected data initially (this makes sure the texture image is
        // created already).
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[lod]);
        }
    
        const GLColor32F kNewMipColors[kMipCount] = {
            GLColor32F(1.0, 1.0, 0.0, 1.0f),
            GLColor32F(1.0, 0.0, 1.0, 1.0f),
            GLColor32F(0.0, 1.0, 1.0, 1.0f),
            GLColor32F(1.0, 1.0, 1.0, 1.0f),
        };
    
        std::array<GLColor32F, getTotalMipDataSize(kMip0Size)> newMipData;
        fillMipData(newMipData.data(), kMip0Size, kNewMipColors);
    
        // Redefine every level with the new format.
        for (size_t mip = 0; mip < kMipCount; ++mip)
        {
            glTexImage2D(GL_TEXTURE_2D, mip, GL_RGBA32F, kMip0Size >> mip, kMip0Size >> mip, 0, GL_RGBA,
                         GL_FLOAT, newMipData.data() + getMipDataOffset(kMip0Size, mip));
        }
    
        // Verify texture colors.
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
    
            GLColor32F mipColor32F = kNewMipColors[lod];
            GLColor mipColor(static_cast<GLubyte>(std::roundf(mipColor32F.R * 255)),
                             static_cast<GLubyte>(std::roundf(mipColor32F.G * 255)),
                             static_cast<GLubyte>(std::roundf(mipColor32F.B * 255)),
                             static_cast<GLubyte>(std::roundf(mipColor32F.A * 255)));
    
            EXPECT_PIXEL_COLOR_EQ(0, 0, mipColor);
        }
    }
    
    // Test that generating mipmaps after incompatibly redefining a level works.
    TEST_P(Texture2DBaseMaxTestES3, GenerateMipmapAfterRedefine)
    {
        initTest();
    
        // Test that all mips have the expected data initially (this makes sure the texture image is
        // created already).
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[lod]);
        }
    
        // Redefine level 1 (any level would do other than 0) to an incompatible size, say the same size
        // as level 0.
        const GLColor kNewMipColor = GLColor::yellow;
        std::array<GLColor, getMipDataSize(kMip0Size, 0)> newMipData;
        std::fill(newMipData.begin(), newMipData.end(), kNewMipColor);
    
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kMip0Size, kMip0Size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     newMipData.data());
    
        // Generate mipmaps.  This should redefine level 1 back to being compatible with level 0.
        glGenerateMipmap(GL_TEXTURE_2D);
    
        // Test that the texture looks as expected.
        const int w = getWindowWidth() - 1;
        const int h = getWindowHeight() - 1;
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[0]);
            EXPECT_PIXEL_COLOR_EQ(w, 0, kMipColors[0]);
            EXPECT_PIXEL_COLOR_EQ(0, h, kMipColors[0]);
            EXPECT_PIXEL_COLOR_EQ(w, h, kMipColors[0]);
        }
    }
    
    // Test that generating mipmaps after incompatibly redefining a level while simultaneously changing
    // the base level works.
    TEST_P(Texture2DBaseMaxTestES3, GenerateMipmapAfterRedefineAndRebase)
    {
        ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsOpenGL());
    
        // http://crbug.com/1100613
        ANGLE_SKIP_TEST_IF(IsNVIDIAShield());
    
        // TODO(anglebug.com/5360): Failing on ARM-based Apple DTKs.
        ANGLE_SKIP_TEST_IF(IsOSX() && IsARM64() && IsDesktopOpenGL());
    
        initTest();
    
        // Test that all mips have the expected data initially (this makes sure the texture image is
        // created already).
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[lod]);
        }
    
        // Redefine level 2 to an incompatible size, say the same size as level 0.
        const GLColor kNewMipColor = GLColor::yellow;
        std::array<GLColor, getMipDataSize(kMip0Size, 0)> newMipData;
        std::fill(newMipData.begin(), newMipData.end(), kNewMipColor);
    
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, kMip0Size, kMip0Size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     newMipData.data());
    
        // Set base level of the texture to 1 then generate mipmaps.  Level 2 that's redefined should
        // go back to being compatibly defined.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        glGenerateMipmap(GL_TEXTURE_2D);
    
        // Test that the texture looks as expected.
        const int w = getWindowWidth() - 1;
        const int h = getWindowHeight() - 1;
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[1]);
            EXPECT_PIXEL_COLOR_EQ(w, 0, kMipColors[1]);
            EXPECT_PIXEL_COLOR_EQ(0, h, kMipColors[1]);
            EXPECT_PIXEL_COLOR_EQ(w, h, kMipColors[1]);
        }
    
        // Redefine level 1 (current base level) to an incompatible size.
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kMip0Size, kMip0Size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     newMipData.data());
    
        // Set base level of the texture back to 0 then generate mipmaps.  Level 1 should go back to
        // being compatibly defined.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glGenerateMipmap(GL_TEXTURE_2D);
    
        // Test that the texture looks as expected.
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[0]);
            EXPECT_PIXEL_COLOR_EQ(w, 0, kMipColors[0]);
            EXPECT_PIXEL_COLOR_EQ(0, h, kMipColors[0]);
            EXPECT_PIXEL_COLOR_EQ(w, h, kMipColors[0]);
        }
    }
    
    // Test that generating mipmaps after incompatibly redefining the base level of the texture works.
    TEST_P(Texture2DBaseMaxTestES3, GenerateMipmapAfterRedefiningBase)
    {
        initTest();
    
        // Test that all mips have the expected data initially (this makes sure the texture image is
        // created already).
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[lod]);
        }
    
        // Redefine level 0 to an incompatible size.
        const GLColor kNewMipColor = GLColor::yellow;
        std::array<GLColor, getMipDataSize(kMip0Size * 2, 0)> newMipData;
        std::fill(newMipData.begin(), newMipData.end(), kNewMipColor);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kMip0Size * 2, kMip0Size * 2, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, newMipData.data());
    
        // Generate mipmaps.
        glGenerateMipmap(GL_TEXTURE_2D);
    
        // Test that the texture looks as expected.
        const int w = getWindowWidth() - 1;
        const int h = getWindowHeight() - 1;
        for (uint32_t lod = 0; lod < kMipCount + 1; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kNewMipColor);
            EXPECT_PIXEL_COLOR_EQ(w, 0, kNewMipColor);
            EXPECT_PIXEL_COLOR_EQ(0, h, kNewMipColor);
            EXPECT_PIXEL_COLOR_EQ(w, h, kNewMipColor);
        }
    }
    
    // Test that generating mipmaps after incompatibly redefining the base level while simultaneously
    // changing MAX_LEVEL works.
    TEST_P(Texture2DBaseMaxTestES3, GenerateMipmapAfterRedefiningBaseAndChangingMax)
    {
        initTest();
    
        // Test that all mips have the expected data initially (this makes sure the texture image is
        // created already).
        for (uint32_t lod = 0; lod < kMipCount; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColors[lod]);
        }
    
        // Redefine level 0 to an incompatible size.
        const GLColor kNewMipColor = GLColor::yellow;
        std::array<GLColor, getMipDataSize(kMip0Size * 2, 0)> newMipData;
        std::fill(newMipData.begin(), newMipData.end(), kNewMipColor);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kMip0Size * 2, kMip0Size * 2, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, newMipData.data());
    
        // Set max level of the texture to 2 then generate mipmaps.
        constexpr uint32_t kMaxLevel = 2;
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, kMaxLevel);
        glGenerateMipmap(GL_TEXTURE_2D);
    
        // Test that the texture looks as expected.
        const int w = getWindowWidth() - 1;
        const int h = getWindowHeight() - 1;
        for (uint32_t lod = 0; lod <= kMaxLevel; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kNewMipColor);
            EXPECT_PIXEL_COLOR_EQ(w, 0, kNewMipColor);
            EXPECT_PIXEL_COLOR_EQ(0, h, kNewMipColor);
            EXPECT_PIXEL_COLOR_EQ(w, h, kNewMipColor);
        }
    }
    
    // Test that stage invalid texture levels work.
    TEST_P(Texture2DBaseMaxTestES3, StageInvalidLevels)
    {
        constexpr uint32_t kMaxLevel           = 2;
        const GLColor kMipColor[kMaxLevel + 1] = {GLColor::red, GLColor::green, GLColor::blue};
    
        initTest();
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        std::vector<GLColor> texDataCyan(2u * 2u, GLColor::cyan);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataCyan.data());
        setLodUniform(0);
        drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
        EXPECT_GL_NO_ERROR();
    
        std::vector<GLColor> texDataGreen(2u * 2u, GLColor::green);
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::blue);
        drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
        EXPECT_GL_NO_ERROR();
    
        std::vector<GLColor> texDataRed(4u * 4u, GLColor::red);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataRed.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
    
        // Test that the texture looks as expected.
        const int w = getWindowWidth() - 1;
        const int h = getWindowHeight() - 1;
        for (uint32_t lod = 0; lod <= kMaxLevel; ++lod)
        {
            setLodUniform(lod);
            drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_PIXEL_COLOR_EQ(0, 0, kMipColor[lod]);
            EXPECT_PIXEL_COLOR_EQ(w, 0, kMipColor[lod]);
            EXPECT_PIXEL_COLOR_EQ(0, h, kMipColor[lod]);
            EXPECT_PIXEL_COLOR_EQ(w, h, kMipColor[lod]);
        }
    }
    
    // Test to check that texture completeness is determined correctly when the texture base level is
    // greater than 0, and also that level 0 is not sampled when base level is greater than 0.
    TEST_P(Texture2DTestES3, DrawWithBaseLevel1)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        std::vector<GLColor> texDataRed(4u * 4u, GLColor::red);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataRed.data());
        std::vector<GLColor> texDataGreen(2u * 2u, GLColor::green);
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Test basic GL_EXT_copy_image copy without any bound textures
    TEST_P(Texture2DTestES3, CopyImage)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_copy_image"));
    
        std::vector<GLColor> texDataRed(4u * 4u, GLColor::red);
        GLTexture srcTexture;
        GLTexture destTexture;
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, destTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataRed.data());
    
        std::vector<GLColor> texDataGreen(4u * 4u, GLColor::green);
        glBindTexture(GL_TEXTURE_2D, srcTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
    
        glBindTexture(GL_TEXTURE_2D, 0);
    
        // copy
        glCopyImageSubDataEXT(srcTexture, GL_TEXTURE_2D, 0, 2, 2, 0, destTexture, GL_TEXTURE_2D, 0, 2,
                              2, 0, 2, 2, 1);
    
        glBindTexture(GL_TEXTURE_2D, destTexture);
    
        EXPECT_GL_NO_ERROR();
    
        glViewport(0, 0, 4, 4);
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
    
        EXPECT_PIXEL_RECT_EQ(2, 2, 2, 2, GLColor::green);
        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 2, GLColor::red);
        EXPECT_PIXEL_RECT_EQ(0, 0, 2, 4, GLColor::red);
    }
    
    // Test GL_EXT_copy_image compressed texture copy with mipmaps smaller than the block size
    TEST_P(Texture2DTestES3, CopyCompressedImageMipMaps)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_copy_image"));
        // TODO(http://anglebug.com/5634): Fix calls to vkCmdCopyBufferToImage() with images smaller
        // than the compressed format block size.
        ANGLE_SKIP_TEST_IF(isAllocateNonZeroMemoryEnabled());
    
        constexpr uint32_t kSize             = 4;
        constexpr size_t kNumLevels          = 3;
        const uint8_t CompressedImageETC1[8] = {0x0, 0x0, 0xf8, 0x2, 0xff, 0xff, 0x0, 0x0};
    
        GLTexture srcTexture;
        glBindTexture(GL_TEXTURE_2D, srcTexture);
        for (size_t level = 0; level < kNumLevels; ++level)
        {
            glCompressedTexImage2D(GL_TEXTURE_2D, level, GL_ETC1_RGB8_OES, kSize >> level,
                                   kSize >> level, 0, 8, CompressedImageETC1);
            EXPECT_GL_NO_ERROR();
        }
    
        GLTexture destTexture;
        glBindTexture(GL_TEXTURE_2D, destTexture);
        for (size_t level = 0; level < kNumLevels; ++level)
        {
            glCompressedTexImage2D(GL_TEXTURE_2D, level, GL_ETC1_RGB8_OES, kSize >> level,
                                   kSize >> level, 0, 8, nullptr);
            EXPECT_GL_NO_ERROR();
        }
    
        glBindTexture(GL_TEXTURE_2D, 0);
    
        // copy
        for (size_t level = 0; level < kNumLevels; ++level)
        {
            glCopyImageSubDataEXT(srcTexture, GL_TEXTURE_2D, level, 0, 0, 0, destTexture, GL_TEXTURE_2D,
                                  level, 0, 0, 0, kSize >> level, kSize >> level, 1);
            EXPECT_GL_NO_ERROR();
        }
    }
    
    // Test GL_EXT_copy_image copy with a non-zero base level
    TEST_P(Texture2DTestES3, CopyImageBaseLevel1)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_copy_image"));
    
        std::vector<GLColor> texDataBlack(8u * 8u, GLColor::black);
        std::vector<GLColor> texDataRed(4u * 4u, GLColor::red);
        std::vector<GLColor> texDataGreen(4u * 4u, GLColor::green);
        std::vector<GLColor> texDataBlue(4u * 4u, GLColor::blue);
    
        GLTexture srcTexture;
        GLTexture destTexture;
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, destTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataBlack.data());
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataRed.data());
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glTexImage2D(GL_TEXTURE_2D, 3, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataBlue.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
    
        glBindTexture(GL_TEXTURE_2D, srcTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataBlack.data());
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataBlue.data());
        glTexImage2D(GL_TEXTURE_2D, 3, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataRed.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
    
        glBindTexture(GL_TEXTURE_2D, 0);
    
        // copy
        glCopyImageSubDataEXT(srcTexture, GL_TEXTURE_2D, 1, 2, 2, 0, destTexture, GL_TEXTURE_2D, 1, 2,
                              2, 0, 2, 2, 1);
    
        glBindTexture(GL_TEXTURE_2D, destTexture);
    
        EXPECT_GL_NO_ERROR();
    
        glViewport(0, 0, 4, 4);
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
    
        EXPECT_PIXEL_RECT_EQ(2, 2, 2, 2, GLColor::green);
        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 2, GLColor::red);
        EXPECT_PIXEL_RECT_EQ(0, 0, 2, 4, GLColor::red);
    }
    
    // Test basic GL_EXT_copy_image copy without any draw calls by attaching the texture
    // to a framebuffer and reads from the framebuffer to validate the copy
    TEST_P(Texture2DTestES3, CopyImageFB)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_copy_image"));
    
        glViewport(0, 0, 4, 4);
        std::vector<GLColor> texDataRed(4u * 4u, GLColor::red);
        GLTexture srcTexture;
        GLTexture destTexture;
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, destTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataRed.data());
    
        std::vector<GLColor> texDataGreen(4u * 4u, GLColor::green);
        glBindTexture(GL_TEXTURE_2D, srcTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glBindTexture(GL_TEXTURE_2D, 0);
    
        // copy
        glCopyImageSubDataEXT(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, destTexture, GL_TEXTURE_2D, 0, 0,
                              1, 0, 3, 3, 1);
    
        EXPECT_GL_NO_ERROR();
    
        GLFramebuffer fb;
        glBindFramebuffer(GL_FRAMEBUFFER, fb);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destTexture, 0);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    
        EXPECT_PIXEL_RECT_EQ(0, 1, 3, 3, GLColor::green);
        EXPECT_PIXEL_RECT_EQ(3, 0, 1, 4, GLColor::red);
        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 1, GLColor::red);
    
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    
    // Test GL_EXT_copy_image copy to a framebuffer attachment after
    // invalidation. Then draw with blending onto the framebuffer.
    TEST_P(Texture2DTestES3, CopyImageFBInvalidateThenBlend)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_copy_image"));
    
        ANGLE_GL_PROGRAM(drawBlueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
        ANGLE_GL_PROGRAM(drawRedProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    
        glViewport(0, 0, 4, 4);
        GLTexture srcTexture;
        GLTexture textureAttachment;
    
        std::vector<GLColor> texDataGreen(4u * 4u, GLColor::green);
        glBindTexture(GL_TEXTURE_2D, srcTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glBindTexture(GL_TEXTURE_2D, 0);
    
        glBindTexture(GL_TEXTURE_2D, textureAttachment);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
        glBindTexture(GL_TEXTURE_2D, 0);
    
        GLFramebuffer fb;
        glBindFramebuffer(GL_FRAMEBUFFER, fb);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureAttachment,
                               0);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    
        // Draw something in the texture to make sure it's image is defined.
        drawQuad(drawRedProgram, essl1_shaders::PositionAttrib(), 0.0f);
    
        // Invalidate the framebuffer.
        const GLenum discards[] = {GL_COLOR_ATTACHMENT0};
        glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, discards);
        ASSERT_GL_NO_ERROR();
    
        // Copy into the framebuffer attachment.
        glCopyImageSubDataEXT(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, textureAttachment, GL_TEXTURE_2D,
                              0, 0, 0, 0, 4, 4, 1);
        EXPECT_GL_NO_ERROR();
    
        // Draw and blend, making sure both the copy and draw happen correctly.
        glEnable(GL_BLEND);
        glBlendFunc(GL_ONE, GL_ONE);
        drawQuad(drawBlueProgram, essl1_shaders::PositionAttrib(), 0.0f);
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::cyan);
    
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    
    // Test that drawing works correctly when levels outside the BASE_LEVEL/MAX_LEVEL range do not
    // have images defined.
    TEST_P(Texture2DTestES3, DrawWithLevelsOutsideRangeUndefined)
    {
        // Observed crashing on AMD. Oddly the crash only happens with 2D textures, not 3D or array.
        ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        std::vector<GLColor> texDataGreen(2u * 2u, GLColor::green);
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Test that drawing works correctly when level 0 is undefined and base level is 1.
    TEST_P(Texture2DTestES3, DrawWithLevelZeroUndefined)
    {
        // Observed crashing on AMD. Oddly the crash only happens with 2D textures, not 3D or array.
        ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        std::vector<GLColor> texDataGreen(2u * 2u, GLColor::green);
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
    
        EXPECT_GL_NO_ERROR();
    
        // Texture is incomplete.
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
    
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
    
        // Texture is now complete.
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Test that drawing works correctly when levels outside the BASE_LEVEL/MAX_LEVEL range have
    // dimensions that don't fit the images inside the range.
    // GLES 3.0.4 section 3.8.13 Texture completeness
    TEST_P(Texture2DTestES3, DrawWithLevelsOutsideRangeWithInconsistentDimensions)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        std::vector<GLColor> texDataRed(8u * 8u, GLColor::red);
        std::vector<GLColor> texDataGreen(2u * 2u, GLColor::green);
        std::vector<GLColor> texDataCyan(2u * 2u, GLColor::cyan);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    
        // Two levels that are initially unused.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataRed.data());
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataCyan.data());
    
        // One level that is used - only this level should affect completeness.
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        ANGLE_SKIP_TEST_IF(IsIntel() && IsWindows() && IsOpenGL());
    
        // Switch the level that is being used to the cyan level 2.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 2);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::cyan);
    }
    
    // Test that drawing works correctly when levels outside the BASE_LEVEL/MAX_LEVEL range do not
    // have images defined.
    TEST_P(Texture3DTestES3, DrawWithLevelsOutsideRangeUndefined)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, mTexture3D);
        std::vector<GLColor> texDataGreen(2u * 2u * 2u, GLColor::green);
        glTexImage3D(GL_TEXTURE_3D, 1, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Test that drawing works correctly when levels outside the BASE_LEVEL/MAX_LEVEL range have
    // dimensions that don't fit the images inside the range.
    // GLES 3.0.4 section 3.8.13 Texture completeness
    TEST_P(Texture3DTestES3, DrawWithLevelsOutsideRangeWithInconsistentDimensions)
    {
        // Crashes on Intel Ubuntu 19.04 Mesa 19.0.2 GL. http://anglebug.com/2782
        ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsDesktopOpenGL());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, mTexture3D);
        std::vector<GLColor> texDataRed(8u * 8u * 8u, GLColor::red);
        std::vector<GLColor> texDataGreen(2u * 2u * 2u, GLColor::green);
        std::vector<GLColor> texDataCyan(2u * 2u * 2u, GLColor::cyan);
    
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    
        // Two levels that are initially unused.
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataRed.data());
        glTexImage3D(GL_TEXTURE_3D, 2, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataCyan.data());
    
        // One level that is used - only this level should affect completeness.
        glTexImage3D(GL_TEXTURE_3D, 1, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
    
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        ANGLE_SKIP_TEST_IF(IsIntel() && IsWindows() && IsOpenGL());
    
        // Switch the level that is being used to the cyan level 2.
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 2);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 2);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::cyan);
    }
    
    // Test that drawing works correctly when levels outside the BASE_LEVEL/MAX_LEVEL range do not
    // have images defined.
    TEST_P(Texture2DArrayTestES3, DrawWithLevelsOutsideRangeUndefined)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
        std::vector<GLColor> texDataGreen(2u * 2u * 2u, GLColor::green);
        glTexImage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Test that drawing works correctly when levels outside the BASE_LEVEL/MAX_LEVEL range have
    // dimensions that don't fit the images inside the range.
    // GLES 3.0.4 section 3.8.13 Texture completeness
    TEST_P(Texture2DArrayTestES3, DrawWithLevelsOutsideRangeWithInconsistentDimensions)
    {
        // TODO(crbug.com/998505): Test failing on Android FYI Release (NVIDIA Shield TV)
        ANGLE_SKIP_TEST_IF(IsNVIDIAShield());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, m2DArrayTexture);
        std::vector<GLColor> texDataRed(8u * 8u * 8u, GLColor::red);
        std::vector<GLColor> texDataGreen(2u * 2u * 2u, GLColor::green);
        std::vector<GLColor> texDataCyan(2u * 2u * 2u, GLColor::cyan);
    
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    
        // Two levels that are initially unused.
        glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataRed.data());
        glTexImage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataCyan.data());
    
        // One level that is used - only this level should affect completeness.
        glTexImage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
    
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        ANGLE_SKIP_TEST_IF(IsIntel() && IsWindows() && IsOpenGL());
    
        // Switch the level that is being used to the cyan level 2.
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, 2);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 2);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::cyan);
    }
    
    // Test that texture completeness is updated if texture max level changes.
    // GLES 3.0.4 section 3.8.13 Texture completeness
    TEST_P(Texture2DTestES3, TextureCompletenessChangesWithMaxLevel)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        std::vector<GLColor> texDataGreen(8u * 8u, GLColor::green);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    
        // A level that is initially unused.
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
    
        // One level that is initially used - only this level should affect completeness.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        // Switch the max level to level 1. The levels within the used range now have inconsistent
        // dimensions and the texture should be incomplete.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
    }
    
    // Test that 3D texture completeness is updated if texture max level changes.
    // GLES 3.0.4 section 3.8.13 Texture completeness
    TEST_P(Texture3DTestES3, Texture3DCompletenessChangesWithMaxLevel)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, mTexture3D);
        std::vector<GLColor> texDataGreen(2u * 2u * 2u, GLColor::green);
    
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    
        // A level that is initially unused.
        glTexImage3D(GL_TEXTURE_3D, 1, GL_RGBA8, 1, 1, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
    
        // One level that is initially used - only this level should affect completeness.
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
    
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 0);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 0);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        // Switch the max level to level 1. The levels within the used range now have inconsistent
        // dimensions and the texture should be incomplete.
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
    }
    
    // Test that texture completeness is updated if texture base level changes.
    // GLES 3.0.4 section 3.8.13 Texture completeness
    TEST_P(Texture2DTestES3, TextureCompletenessChangesWithBaseLevel)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        std::vector<GLColor> texDataGreen(8u * 8u, GLColor::green);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    
        // Two levels that are initially unused.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
    
        // One level that is initially used - only this level should affect completeness.
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataGreen.data());
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 2);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        // Switch the base level to level 1. The levels within the used range now have inconsistent
        // dimensions and the texture should be incomplete.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
    }
    
    // Test that texture is not complete if base level is greater than max level.
    // GLES 3.0.4 section 3.8.13 Texture completeness
    TEST_P(Texture2DTestES3, TextureBaseLevelGreaterThanMaxLevel)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::green);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 10000);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        // Texture should be incomplete.
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
    }
    
    // Test that immutable texture base level and max level are clamped.
    // GLES 3.0.4 section 3.8.10 subsection Mipmapping
    TEST_P(Texture2DTestES3, ImmutableTextureBaseLevelOutOfRange)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        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);
    
        // For immutable-format textures, base level should be clamped to [0, levels - 1], and max level
        // should be clamped to [base_level, levels - 1].
        // GLES 3.0.4 section 3.8.10 subsection Mipmapping
        // In the case of this test, those rules make the effective base level and max level 0.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 10000);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 10000);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        // Texture should be complete.
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Test that changing base level works when it affects the format of the texture.
    TEST_P(Texture2DTestES3, TextureFormatChangesWithBaseLevel)
    {
        // TODO(crbug.com/998505): Test failing on Android FYI Release (NVIDIA Shield TV)
        ANGLE_SKIP_TEST_IF(IsNVIDIAShield());
    
        ANGLE_SKIP_TEST_IF(IsIntel() && IsWindows() && IsDesktopOpenGL());
    
        // Observed incorrect rendering on AMD OpenGL.
        ANGLE_SKIP_TEST_IF(IsAMD() && IsDesktopOpenGL());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        std::vector<GLColor> texDataCyan(4u * 4u, GLColor::cyan);
        std::vector<GLColor> texDataGreen(4u * 4u, GLColor::green);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    
        // RGBA8 level that's initially unused.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texDataCyan.data());
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
    
        // RG8 level that's initially used, with consistent dimensions with level 0 but a different
        // format. It reads green channel data from the green and alpha channels of texDataGreen
        // (this is a bit hacky but works).
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RG8, 2, 2, 0, GL_RG, GL_UNSIGNED_BYTE, texDataGreen.data());
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        // Switch the texture to use the cyan level 0 with the RGBA format.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::cyan);
    }
    
    // Test that setting a texture image works when base level is out of range.
    TEST_P(Texture2DTestES3, SetImageWhenBaseLevelOutOfRange)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 10000);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 10000);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::green);
    
        EXPECT_GL_NO_ERROR();
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
    
        drawQuad(mProgram, "position", 0.5f);
    
        // Texture should be complete.
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // In the D3D11 renderer, we need to initialize some texture formats, to fill empty channels. EG
    // RBA->RGBA8, with 1.0 in the alpha channel. This test covers a bug where redefining array textures
    // with these formats does not work as expected.
    TEST_P(Texture2DArrayTestES3, RedefineInittableArray)
    {
        std::vector<GLubyte> pixelData;
        for (size_t count = 0; count < 5000; count++)
        {
            pixelData.push_back(0u);
            pixelData.push_back(255u);
            pixelData.push_back(0u);
        }
    
        glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
        glUseProgram(mProgram);
        glUniform1i(mTextureArrayLocation, 0);
    
        // The first draw worked correctly.
        glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGB, 4, 4, 2, 0, GL_RGB, GL_UNSIGNED_BYTE,
                     &pixelData[0]);
    
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
        drawQuad(mProgram, "position", 1.0f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        // The dimension of the respecification must match the original exactly to trigger the bug.
        glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGB, 4, 4, 2, 0, GL_RGB, GL_UNSIGNED_BYTE,
                     &pixelData[0]);
        drawQuad(mProgram, "position", 1.0f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        ASSERT_GL_NO_ERROR();
    }
    
    // Test shadow sampler and regular non-shadow sampler coexisting in the same shader.
    // This test is needed especially to confirm that sampler registers get assigned correctly on
    // the HLSL backend even when there's a mix of different HLSL sampler and texture types.
    TEST_P(ShadowSamplerPlusSampler3DTestES3, ShadowSamplerPlusSampler3DDraw)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, mTexture3D);
        GLubyte texData[4];
        texData[0] = 0;
        texData[1] = 60;
        texData[2] = 0;
        texData[3] = 255;
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, mTextureShadow);
        GLfloat depthTexData[1];
        depthTexData[0] = 0.5f;
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, 1, 1, 0, GL_DEPTH_COMPONENT, GL_FLOAT,
                     depthTexData);
    
        glUseProgram(mProgram);
        glUniform1f(mDepthRefUniformLocation, 0.3f);
        glUniform1i(mTexture3DUniformLocation, 0);
        glUniform1i(mTextureShadowUniformLocation, 1);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        // The shader writes 0.5 * <comparison result (1.0)> + <texture color>
        EXPECT_PIXEL_NEAR(0, 0, 128, 188, 128, 255, 2);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_GREATER);
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        // The shader writes 0.5 * <comparison result (0.0)> + <texture color>
        EXPECT_PIXEL_NEAR(0, 0, 0, 60, 0, 255, 2);
    }
    
    // Test multiple different sampler types in the same shader.
    // This test makes sure that even if sampler / texture registers get grouped together based on type
    // or otherwise get shuffled around in the HLSL backend of the shader translator, the D3D renderer
    // still has the right register index information for each ESSL sampler.
    // The tested ESSL samplers have the following types in D3D11 HLSL:
    // sampler2D:         Texture2D   + SamplerState
    // samplerCube:       TextureCube + SamplerState
    // sampler2DShadow:   Texture2D   + SamplerComparisonState
    // samplerCubeShadow: TextureCube + SamplerComparisonState
    TEST_P(SamplerTypeMixTestES3, SamplerTypeMixDraw)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        GLubyte texData[4];
        texData[0] = 0;
        texData[1] = 0;
        texData[2] = 120;
        texData[3] = 255;
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
        texData[0] = 0;
        texData[1] = 90;
        texData[2] = 0;
        texData[3] = 255;
        glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_RGBA8, 1, 1);
        glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        texData);
    
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, mTexture2DShadow);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
        GLfloat depthTexData[1];
        depthTexData[0] = 0.5f;
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, 1, 1, 0, GL_DEPTH_COMPONENT, GL_FLOAT,
                     depthTexData);
    
        glActiveTexture(GL_TEXTURE3);
        glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCubeShadow);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
        depthTexData[0] = 0.2f;
        glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_DEPTH_COMPONENT32F, 1, 1);
        glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
                        depthTexData);
    
        // http://anglebug.com/3949: TODO: Add a DS texture case
    
        EXPECT_GL_NO_ERROR();
    
        glUseProgram(mProgram);
        glUniform1f(mDepthRefUniformLocation, 0.3f);
        glUniform1i(mTexture2DUniformLocation, 0);
        glUniform1i(mTextureCubeUniformLocation, 1);
        glUniform1i(mTexture2DShadowUniformLocation, 2);
        glUniform1i(mTextureCubeShadowUniformLocation, 3);
    
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        // The shader writes:
        // <texture 2d color> +
        // <cube map color> +
        // 0.25 * <comparison result (1.0)> +
        // 0.125 * <comparison result (0.0)>
        EXPECT_PIXEL_NEAR(0, 0, 64, 154, 184, 255, 2);
    }
    
    // Test different base levels on textures accessed through the same sampler array.
    // Calling textureSize() on the samplers hits the D3D sampler metadata workaround.
    TEST_P(TextureSizeTextureArrayTest, BaseLevelVariesInTextureArray)
    {
        ANGLE_SKIP_TEST_IF(IsAMD() && IsD3D11());
    
        // http://anglebug.com/4391
        ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsWindows() && IsD3D11());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2DA);
        GLsizei size = 64;
        for (GLint level = 0; level < 7; ++level)
        {
            ASSERT_LT(0, size);
            glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         nullptr);
            size = size / 2;
        }
        ASSERT_EQ(0, size);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
    
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, mTexture2DB);
        size = 128;
        for (GLint level = 0; level < 8; ++level)
        {
            ASSERT_LT(0, size);
            glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         nullptr);
            size = size / 2;
        }
        ASSERT_EQ(0, size);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 3);
        EXPECT_GL_NO_ERROR();
    
        glUseProgram(mProgram);
        glUniform1i(mTexture0Location, 0);
        glUniform1i(mTexture1Location, 1);
    
        drawQuad(mProgram, essl3_shaders::PositionAttrib(), 0.5f);
        EXPECT_GL_NO_ERROR();
        // Red channel: width of level 1 of texture A: 32.
        // Green channel: width of level 3 of texture B: 16.
        EXPECT_PIXEL_NEAR(0, 0, 32, 16, 0, 255, 2);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DTestES3, TextureRGBImplicitAlpha1)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DTest, TextureLuminanceImplicitAlpha1)
    {
        setUpProgram();
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 1, 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // Validate that every component of the pixel will be equal to the luminance value we've set
    // and that the alpha channel will be 1 (or 255 to be exact).
    TEST_P(Texture2DTest, TextureLuminanceRGBSame)
    {
        setUpProgram();
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        uint8_t pixel = 50;
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 1, 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, &pixel);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(pixel, pixel, pixel, 255));
    }
    
    // Validate that every component of the pixel will be equal to the luminance value we've set
    // and that the alpha channel will be the second component.
    TEST_P(Texture2DTest, TextureLuminanceAlphaRGBSame)
    {
        setUpProgram();
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        uint8_t pixel[] = {50, 25};
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, 1, 1, 0, GL_LUMINANCE_ALPHA,
                     GL_UNSIGNED_BYTE, pixel);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(pixel[0], pixel[0], pixel[0], pixel[1]));
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DTest, TextureLuminance32ImplicitAlpha1)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float"));
        ANGLE_SKIP_TEST_IF(IsD3D9());
        ANGLE_SKIP_TEST_IF(IsVulkan());
    
        setUpProgram();
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 1, 1, 0, GL_LUMINANCE, GL_FLOAT, nullptr);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DTest, TextureLuminance16ImplicitAlpha1)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float"));
        ANGLE_SKIP_TEST_IF(IsD3D9());
        ANGLE_SKIP_TEST_IF(IsVulkan());
        // TODO(ynovikov): re-enable once root cause of http://anglebug.com/1420 is fixed
        ANGLE_SKIP_TEST_IF(IsAndroid() && IsAdreno() && IsOpenGLES());
    
        setUpProgram();
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 1, 1, 0, GL_LUMINANCE, GL_HALF_FLOAT_OES, nullptr);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DUnsignedIntegerAlpha1TestES3, TextureRGB8UIImplicitAlpha1)
    {
        ANGLE_SKIP_TEST_IF(IsIntel() && IsOSX());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8UI, 1, 1, 0, GL_RGB_INTEGER, GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DIntegerAlpha1TestES3, TextureRGB8IImplicitAlpha1)
    {
        ANGLE_SKIP_TEST_IF(IsIntel() && IsOSX());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8I, 1, 1, 0, GL_RGB_INTEGER, GL_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DUnsignedIntegerAlpha1TestES3, TextureRGB16UIImplicitAlpha1)
    {
        ANGLE_SKIP_TEST_IF(IsIntel() && IsOSX());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16UI, 1, 1, 0, GL_RGB_INTEGER, GL_UNSIGNED_SHORT, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DIntegerAlpha1TestES3, TextureRGB16IImplicitAlpha1)
    {
        ANGLE_SKIP_TEST_IF(IsIntel() && IsOSX());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16I, 1, 1, 0, GL_RGB_INTEGER, GL_SHORT, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DUnsignedIntegerAlpha1TestES3, TextureRGB32UIImplicitAlpha1)
    {
        ANGLE_SKIP_TEST_IF(IsIntel() && IsOSX());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32UI, 1, 1, 0, GL_RGB_INTEGER, GL_UNSIGNED_INT, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DIntegerAlpha1TestES3, TextureRGB32IImplicitAlpha1)
    {
        ANGLE_SKIP_TEST_IF(IsIntel() && IsOSX());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32I, 1, 1, 0, GL_RGB_INTEGER, GL_INT, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DTestES3, TextureRGBSNORMImplicitAlpha1)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8_SNORM, 1, 1, 0, GL_RGB, GL_BYTE, nullptr);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DTestES3, TextureRGB9E5ImplicitAlpha1)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB9_E5, 1, 1, 0, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV,
                     nullptr);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DTestES3, TextureCOMPRESSEDRGB8ETC2ImplicitAlpha1)
    {
        // ETC texture formats are not supported on Mac OpenGL. http://anglebug.com/3853
        ANGLE_SKIP_TEST_IF(IsOSX() && IsDesktopOpenGL());
    
        // http://anglebug.com/5187
        ANGLE_SKIP_TEST_IF(IsOSX() && IsMetal());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB8_ETC2, 1, 1, 0, 8, nullptr);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // When sampling a texture without an alpha channel, "1" is returned as the alpha value.
    // ES 3.0.4 table 3.24
    TEST_P(Texture2DTestES3, TextureCOMPRESSEDSRGB8ETC2ImplicitAlpha1)
    {
        // ETC texture formats are not supported on Mac OpenGL. http://anglebug.com/3853
        ANGLE_SKIP_TEST_IF(IsOSX() && IsDesktopOpenGL());
    
        // http://anglebug.com/5187
        ANGLE_SKIP_TEST_IF(IsOSX() && IsMetal());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_SRGB8_ETC2, 1, 1, 0, 8, nullptr);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
    }
    
    // Copied from Texture2DTest::TexStorage
    // Test that glTexSubImage2D works properly when glTexStorage2DEXT has initialized the image with a
    // default color.
    TEST_P(Texture2DTestES31PPO, TexStorage)
    {
        ANGLE_SKIP_TEST_IF(!IsVulkan());
        ANGLE_SKIP_TEST_IF((getClientMajorVersion() < 3 && getClientMinorVersion() < 1) &&
                           !IsGLExtensionEnabled("GL_EXT_texture_storage"));
    
        const char *vertexShaderSource   = getVertexShaderSource();
        const char *fragmentShaderSource = getFragmentShaderSource();
    
        bindProgramPipeline(vertexShaderSource, fragmentShaderSource);
        mTexture2DUniformLocation = glGetUniformLocation(mFragProg, getTextureUniformName());
    
        int width  = getWindowWidth();
        int height = getWindowHeight();
    
        GLuint tex2D;
        glGenTextures(1, &tex2D);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex2D);
    
        // Fill with red
        std::vector<GLubyte> pixels(3 * 16 * 16);
        for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
        {
            pixels[pixelId * 3 + 0] = 255;
            pixels[pixelId * 3 + 1] = 0;
            pixels[pixelId * 3 + 2] = 0;
        }
    
        // ANGLE internally uses RGBA as the internal format for RGB images, therefore glTexStorage2DEXT
        // initializes the image to a default color to get a consistent alpha color. The data is kept in
        // a CPU-side image and the image is marked as dirty.
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB8, 16, 16);
    
        // Initializes the color of the upper-left 8x8 pixels, leaves the other pixels untouched.
        // glTexSubImage2D should take into account that the image is dirty.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
        glActiveShaderProgram(mPipeline, mFragProg);
        glUniform1i(mTexture2DUniformLocation, 0);
    
        std::array<Vector3, 6> quadVertices = ANGLETestBase::GetQuadVertices();
        ppoDrawQuad(quadVertices, "position", 0.5f, 1.0f);
    
        glDeleteTextures(1, &tex2D);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
    
        // Validate that the region of the texture without data has an alpha of 1.0
        angle::GLColor pixel = ReadColor(3 * width / 4, 3 * height / 4);
        EXPECT_EQ(255, pixel.A);
    }
    
    // Copied from Texture2DTestES3::SingleTextureMultipleSamplers
    // Tests behaviour with a single texture and multiple sampler objects.
    TEST_P(Texture2DTestES31PPO, SingleTextureMultipleSamplers)
    {
        ANGLE_SKIP_TEST_IF(!IsVulkan());
    
        const char *vertexShaderSource   = getVertexShaderSource();
        const char *fragmentShaderSource = getFragmentShaderSource();
    
        bindProgramPipeline(vertexShaderSource, fragmentShaderSource);
        mTexture2DUniformLocation = glGetUniformLocation(mFragProg, getTextureUniformName());
    
        GLint maxTextureUnits = 0;
        glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
        ANGLE_SKIP_TEST_IF(maxTextureUnits < 4);
    
        constexpr int kSize                 = 16;
        std::array<Vector3, 6> quadVertices = ANGLETestBase::GetQuadVertices();
    
        // Make a single-level texture, fill it with red.
        std::vector<GLColor> redColors(kSize * kSize, GLColor::red);
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     redColors.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // Simple confidence check.
        bind2DTexturedQuadProgramPipeline();
        ppoDrawQuad(quadVertices, "position", 0.5f, 1.0f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    
        // Bind texture to unit 1 with a sampler object making it incomplete.
        GLSampler sampler;
        glBindSampler(0, sampler);
        glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // Make a mipmap texture, fill it with blue.
        std::vector<GLColor> blueColors(kSize * kSize, GLColor::blue);
        GLTexture mipmapTex;
        glBindTexture(GL_TEXTURE_2D, mipmapTex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     blueColors.data());
        glGenerateMipmap(GL_TEXTURE_2D);
    
        // Draw with the sampler, expect blue.
        draw2DTexturedQuad(0.5f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    
        // Simple multitexturing program.
        constexpr char kVS[] =
            "#version 310 es\n"
            "precision mediump float;\n"
            "in vec2 position;\n"
            "out vec2 texCoord;\n"
            "void main()\n"
            "{\n"
            "    gl_Position = vec4(position, 0, 1);\n"
            "    texCoord = position * 0.5 + vec2(0.5);\n"
            "}";
    
        constexpr char kFS[] =
            "#version 310 es\n"
            "precision mediump float;\n"
            "in vec2 texCoord;\n"
            "uniform sampler2D tex1;\n"
            "uniform sampler2D tex2;\n"
            "uniform sampler2D tex3;\n"
            "uniform sampler2D tex4;\n"
            "out vec4 color;\n"
            "void main()\n"
            "{\n"
            "    color = (texture(tex1, texCoord) + texture(tex2, texCoord) \n"
            "          +  texture(tex3, texCoord) + texture(tex4, texCoord)) * 0.25;\n"
            "}";
    
        bindProgramPipeline(kVS, kFS);
    
        std::array<GLint, 4> texLocations = {
            {glGetUniformLocation(mFragProg, "tex1"), glGetUniformLocation(mFragProg, "tex2"),
             glGetUniformLocation(mFragProg, "tex3"), glGetUniformLocation(mFragProg, "tex4")}};
        for (GLint location : texLocations)
        {
            ASSERT_NE(-1, location);
        }
    
        // Init the uniform data.
        glActiveShaderProgram(mPipeline, mFragProg);
        for (GLint location = 0; location < 4; ++location)
        {
            glUniform1i(texLocations[location], location);
        }
    
        // Initialize four samplers
        GLSampler samplers[4];
    
        // 0: non-mipped.
        glBindSampler(0, samplers[0]);
        glSamplerParameteri(samplers[0], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glSamplerParameteri(samplers[0], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // 1: mipped.
        glBindSampler(1, samplers[1]);
        glSamplerParameteri(samplers[1], GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glSamplerParameteri(samplers[1], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // 2: non-mipped.
        glBindSampler(2, samplers[2]);
        glSamplerParameteri(samplers[2], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glSamplerParameteri(samplers[2], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // 3: mipped.
        glBindSampler(3, samplers[3]);
        glSamplerParameteri(samplers[3], GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glSamplerParameteri(samplers[3], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // Bind two blue mipped textures and two single layer textures, should all draw.
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex);
    
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, mipmapTex);
    
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, tex);
    
        glActiveTexture(GL_TEXTURE3);
        glBindTexture(GL_TEXTURE_2D, mipmapTex);
    
        ASSERT_GL_NO_ERROR();
    
        ppoDrawQuad(quadVertices, "position", 0.5f, 1.0f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_NEAR(0, 0, 128, 0, 128, 255, 2);
    
        // Bind four single layer textures, two should be incomplete.
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, tex);
    
        glActiveTexture(GL_TEXTURE3);
        glBindTexture(GL_TEXTURE_2D, tex);
    
        ppoDrawQuad(quadVertices, "position", 0.5f, 1.0f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_NEAR(0, 0, 128, 0, 0, 255, 2);
    }
    
    // Use a sampler in a uniform struct.
    TEST_P(SamplerInStructTest, SamplerInStruct)
    {
        runSamplerInStructTest();
    }
    
    // Use a sampler in a uniform struct that's passed as a function parameter.
    TEST_P(SamplerInStructAsFunctionParameterTest, SamplerInStructAsFunctionParameter)
    {
        // Fails on Nexus 5X due to a driver bug. http://anglebug.com/1427
        ANGLE_SKIP_TEST_IF(IsNexus5X() && IsOpenGLES());
    
        runSamplerInStructTest();
    }
    
    // Use a sampler in a uniform struct array with a struct from the array passed as a function
    // parameter.
    TEST_P(SamplerInStructArrayAsFunctionParameterTest, SamplerInStructArrayAsFunctionParameter)
    {
        // Fails on Nexus 5X due to a driver bug. http://anglebug.com/1427
        ANGLE_SKIP_TEST_IF(IsNexus5X() && IsOpenGLES());
    
        runSamplerInStructTest();
    }
    
    // Use a sampler in a struct inside a uniform struct with the nested struct passed as a function
    // parameter.
    TEST_P(SamplerInNestedStructAsFunctionParameterTest, SamplerInNestedStructAsFunctionParameter)
    {
        // Fails on Nexus 5X due to a driver bug. http://anglebug.com/1427
        ANGLE_SKIP_TEST_IF(IsNexus5X() && IsOpenGLES());
    
        runSamplerInStructTest();
    }
    
    // Make sure that there isn't a name conflict between sampler extracted from a struct and a
    // similarly named uniform.
    TEST_P(SamplerInStructAndOtherVariableTest, SamplerInStructAndOtherVariable)
    {
        runSamplerInStructTest();
    }
    
    // GL_EXT_texture_filter_anisotropic
    class TextureAnisotropyTest : public Texture2DTest
    {
      protected:
        void uploadTexture()
        {
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, mTexture2D);
            GLColor texDataRed[1] = {GLColor::red};
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texDataRed);
            EXPECT_GL_NO_ERROR();
        }
    };
    
    // Tests that setting anisotropic filtering doesn't cause failures at draw time.
    TEST_P(TextureAnisotropyTest, AnisotropyFunctional)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_filter_anisotropic"));
    
        setUpProgram();
    
        uploadTexture();
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0f);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::red);
    }
    
    // GL_OES_texture_border_clamp
    class TextureBorderClampTest : public Texture2DTest
    {
      protected:
        TextureBorderClampTest() : Texture2DTest() {}
    
        const char *getVertexShaderSource() override
        {
            return
                R"(precision highp float;
                attribute vec4 position;
                varying vec2 texcoord;
    
                void main()
                {
                    gl_Position = vec4(position.xy, 0.0, 1.0);
                    // texcoords in [-0.5, 1.5]
                    texcoord = (position.xy) + 0.5;
                })";
        }
    
        void uploadTexture()
        {
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, mTexture2D);
            std::vector<GLColor> texDataRed(1, GLColor::red);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         texDataRed.data());
            EXPECT_GL_NO_ERROR();
        }
    };
    
    // Test if the color set as GL_TEXTURE_BORDER_COLOR is used when sampling outside of the texture in
    // GL_CLAMP_TO_BORDER wrap mode (set with glTexParameter).
    TEST_P(TextureBorderClampTest, TextureBorderClampFunctional)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_border_clamp"));
    
        setUpProgram();
    
        uploadTexture();
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, &kFloatGreen.R);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green);
    }
    
    // Test reading back GL_TEXTURE_BORDER_COLOR by glGetTexParameter.
    TEST_P(TextureBorderClampTest, TextureBorderClampFunctional2)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_border_clamp"));
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, &kFloatGreen.R);
    
        GLint colorFixedPoint[4] = {0};
        glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, colorFixedPoint);
        constexpr GLint colorGreenFixedPoint[4] = {0, std::numeric_limits<GLint>::max(), 0,
                                                   std::numeric_limits<GLint>::max()};
        EXPECT_EQ(colorFixedPoint[0], colorGreenFixedPoint[0]);
        EXPECT_EQ(colorFixedPoint[1], colorGreenFixedPoint[1]);
        EXPECT_EQ(colorFixedPoint[2], colorGreenFixedPoint[2]);
        EXPECT_EQ(colorFixedPoint[3], colorGreenFixedPoint[3]);
    
        constexpr GLint colorBlueFixedPoint[4] = {0, 0, std::numeric_limits<GLint>::max(),
                                                  std::numeric_limits<GLint>::max()};
        glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, colorBlueFixedPoint);
    
        GLfloat color[4] = {0.0f};
        glGetTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);
        EXPECT_EQ(color[0], kFloatBlue.R);
        EXPECT_EQ(color[1], kFloatBlue.G);
        EXPECT_EQ(color[2], kFloatBlue.B);
        EXPECT_EQ(color[3], kFloatBlue.A);
    }
    
    // Test GL_TEXTURE_BORDER_COLOR parameter validation at glTexParameter.
    TEST_P(TextureBorderClampTest, TextureBorderClampValidation)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_border_clamp"));
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, 1.0f);
        EXPECT_GL_ERROR(GL_INVALID_ENUM);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, std::numeric_limits<GLint>::max());
        EXPECT_GL_ERROR(GL_INVALID_ENUM);
    
        glTexParameterfv(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_BORDER_COLOR, &kFloatGreen.R);
        EXPECT_GL_ERROR(GL_INVALID_ENUM);
    
        GLint colorInt[4] = {0};
        glTexParameteriv(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, GL_TEXTURE_BORDER_COLOR, colorInt);
        EXPECT_GL_ERROR(GL_INVALID_ENUM);
    
        if (getClientMajorVersion() < 3)
        {
            glTexParameterIivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, colorInt);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
            glGetTexParameterIivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, colorInt);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    
            GLuint colorUInt[4] = {0};
            glTexParameterIuivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, colorUInt);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
            glGetTexParameterIuivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, colorUInt);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    
            GLSampler sampler;
            glSamplerParameterIivOES(sampler, GL_TEXTURE_BORDER_COLOR, colorInt);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
            glGetSamplerParameterIivOES(sampler, GL_TEXTURE_BORDER_COLOR, colorInt);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    
            glSamplerParameterIuivOES(sampler, GL_TEXTURE_BORDER_COLOR, colorUInt);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
            glGetSamplerParameterIuivOES(sampler, GL_TEXTURE_BORDER_COLOR, colorUInt);
            EXPECT_GL_ERROR(GL_INVALID_OPERATION);
        }
    }
    
    class TextureBorderClampTestES3 : public TextureBorderClampTest
    {
      protected:
        TextureBorderClampTestES3() : TextureBorderClampTest() {}
    };
    
    // Test if the color set as GL_TEXTURE_BORDER_COLOR is used when sampling outside of the texture in
    // GL_CLAMP_TO_BORDER wrap mode (set with glSamplerParameter).
    TEST_P(TextureBorderClampTestES3, TextureBorderClampES3Functional)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_border_clamp"));
    
        setUpProgram();
    
        uploadTexture();
    
        GLSampler sampler;
        glBindSampler(0, sampler);
        glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
        glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
        glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, &kFloatGreen.R);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green);
    }
    
    // Test reading back GL_TEXTURE_BORDER_COLOR by glGetSamplerParameter.
    TEST_P(TextureBorderClampTestES3, TextureBorderClampES3Functional2)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_border_clamp"));
    
        glActiveTexture(GL_TEXTURE0);
    
        GLSampler sampler;
        glBindSampler(0, sampler);
    
        glSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, &kFloatGreen.R);
    
        GLint colorFixedPoint[4] = {0};
        glGetSamplerParameteriv(sampler, GL_TEXTURE_BORDER_COLOR, colorFixedPoint);
        constexpr GLint colorGreenFixedPoint[4] = {0, std::numeric_limits<GLint>::max(), 0,
                                                   std::numeric_limits<GLint>::max()};
        EXPECT_EQ(colorFixedPoint[0], colorGreenFixedPoint[0]);
        EXPECT_EQ(colorFixedPoint[1], colorGreenFixedPoint[1]);
        EXPECT_EQ(colorFixedPoint[2], colorGreenFixedPoint[2]);
        EXPECT_EQ(colorFixedPoint[3], colorGreenFixedPoint[3]);
    
        constexpr GLint colorBlueFixedPoint[4] = {0, 0, std::numeric_limits<GLint>::max(),
                                                  std::numeric_limits<GLint>::max()};
        glSamplerParameteriv(sampler, GL_TEXTURE_BORDER_COLOR, colorBlueFixedPoint);
    
        GLfloat color[4] = {0.0f};
        glGetSamplerParameterfv(sampler, GL_TEXTURE_BORDER_COLOR, color);
        EXPECT_EQ(color[0], kFloatBlue.R);
        EXPECT_EQ(color[1], kFloatBlue.G);
        EXPECT_EQ(color[2], kFloatBlue.B);
        EXPECT_EQ(color[3], kFloatBlue.A);
    
        constexpr GLint colorSomewhatRedInt[4] = {500000, 0, 0, std::numeric_limits<GLint>::max()};
        glSamplerParameterIivOES(sampler, GL_TEXTURE_BORDER_COLOR, colorSomewhatRedInt);
        GLint colorInt[4] = {0};
        glGetSamplerParameterIivOES(sampler, GL_TEXTURE_BORDER_COLOR, colorInt);
        EXPECT_EQ(colorInt[0], colorSomewhatRedInt[0]);
        EXPECT_EQ(colorInt[1], colorSomewhatRedInt[1]);
        EXPECT_EQ(colorInt[2], colorSomewhatRedInt[2]);
        EXPECT_EQ(colorInt[3], colorSomewhatRedInt[3]);
    
        constexpr GLuint colorSomewhatRedUInt[4] = {500000, 0, 0, std::numeric_limits<GLuint>::max()};
        glSamplerParameterIuivOES(sampler, GL_TEXTURE_BORDER_COLOR, colorSomewhatRedUInt);
        GLuint colorUInt[4] = {0};
        glGetSamplerParameterIuivOES(sampler, GL_TEXTURE_BORDER_COLOR, colorUInt);
        EXPECT_EQ(colorUInt[0], colorSomewhatRedUInt[0]);
        EXPECT_EQ(colorUInt[1], colorSomewhatRedUInt[1]);
        EXPECT_EQ(colorUInt[2], colorSomewhatRedUInt[2]);
        EXPECT_EQ(colorUInt[3], colorSomewhatRedUInt[3]);
    
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        constexpr GLint colorSomewhatGreenInt[4] = {0, 500000, 0, std::numeric_limits<GLint>::max()};
        glTexParameterIivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, colorSomewhatGreenInt);
        glGetTexParameterIivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, colorInt);
        EXPECT_EQ(colorInt[0], colorSomewhatGreenInt[0]);
        EXPECT_EQ(colorInt[1], colorSomewhatGreenInt[1]);
        EXPECT_EQ(colorInt[2], colorSomewhatGreenInt[2]);
        EXPECT_EQ(colorInt[3], colorSomewhatGreenInt[3]);
    
        constexpr GLuint colorSomewhatGreenUInt[4] = {0, 500000, 0, std::numeric_limits<GLuint>::max()};
        glTexParameterIuivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, colorSomewhatGreenUInt);
        glGetTexParameterIuivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, colorUInt);
        EXPECT_EQ(colorUInt[0], colorSomewhatGreenUInt[0]);
        EXPECT_EQ(colorUInt[1], colorSomewhatGreenUInt[1]);
        EXPECT_EQ(colorUInt[2], colorSomewhatGreenUInt[2]);
        EXPECT_EQ(colorUInt[3], colorSomewhatGreenUInt[3]);
    }
    
    // Test GL_TEXTURE_BORDER_COLOR parameter validation at glSamplerParameter.
    TEST_P(TextureBorderClampTestES3, TextureBorderClampES3Validation)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_border_clamp"));
    
        glActiveTexture(GL_TEXTURE0);
    
        GLSampler sampler;
        glBindSampler(0, sampler);
    
        glSamplerParameterf(sampler, GL_TEXTURE_BORDER_COLOR, 1.0f);
        EXPECT_GL_ERROR(GL_INVALID_ENUM);
    
        glSamplerParameteri(sampler, GL_TEXTURE_BORDER_COLOR, std::numeric_limits<GLint>::max());
        EXPECT_GL_ERROR(GL_INVALID_ENUM);
    }
    
    class TextureBorderClampIntegerTestES3 : public Texture2DTest
    {
      protected:
        TextureBorderClampIntegerTestES3() : Texture2DTest(), isUnsignedIntTest(false) {}
    
        const char *getVertexShaderSource() override
        {
            return
                R"(#version 300 es
                out vec2 texcoord;
                in vec4 position;
    
                void main()
                {
                    gl_Position = vec4(position.xy, 0.0, 1.0);
                    // texcoords in [-0.5, 1.5]
                    texcoord = (position.xy) + 0.5;
                })";
        }
    
        const char *getFragmentShaderSource() override
        {
            if (isUnsignedIntTest)
            {
                return "#version 300 es\n"
                       "precision highp float;\n"
                       "uniform highp usampler2D tex;\n"
                       "in vec2 texcoord;\n"
                       "out vec4 fragColor;\n"
    
                       "void main()\n"
                       "{\n"
                       "vec4 red   = vec4(1.0, 0.0, 0.0, 1.0);\n"
                       "vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
                       "fragColor = (texture(tex, texcoord).r == 150u)"
                       "            ? green : red;\n"
                       "}\n";
            }
            else
            {
                return "#version 300 es\n"
                       "precision highp float;\n"
                       "uniform highp isampler2D tex;\n"
                       "in vec2 texcoord;\n"
                       "out vec4 fragColor;\n"
    
                       "void main()\n"
                       "{\n"
                       "vec4 red   = vec4(1.0, 0.0, 0.0, 1.0);\n"
                       "vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
                       "fragColor = (texture(tex, texcoord).r == -50)"
                       "            ? green : red;\n"
                       "}\n";
            }
        }
    
        void uploadTexture()
        {
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, mTexture2D);
            if (isUnsignedIntTest)
            {
                std::vector<GLubyte> texData(4, 100);
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, 1, 1, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE,
                             texData.data());
            }
            else
            {
                std::vector<GLbyte> texData(4, 100);
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8I, 1, 1, 0, GL_RGBA_INTEGER, GL_BYTE,
                             texData.data());
            }
            EXPECT_GL_NO_ERROR();
        }
    
        bool isUnsignedIntTest;
    };
    
    // Test if the integer values set as GL_TEXTURE_BORDER_COLOR is used when sampling outside of the
    // integer texture in GL_CLAMP_TO_BORDER wrap mode (set with glTexParameterIivOES).
    TEST_P(TextureBorderClampIntegerTestES3, TextureBorderClampInteger)
    {
        // Fails on Win10 FYI x64 Release (AMD RX 550). http://anglebug.com/3760
        ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsDesktopOpenGL());
    
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_border_clamp"));
    
        setUpProgram();
    
        uploadTexture();
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        constexpr GLint borderColor[4] = {-50, -50, -50, -50};
        glTexParameterIivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green);
    }
    
    // Test if the integer values set as GL_TEXTURE_BORDER_COLOR is used when sampling outside of the
    // integer texture in GL_CLAMP_TO_BORDER wrap mode (set with glTexParameterIivOES).
    TEST_P(TextureBorderClampIntegerTestES3, TextureBorderClampInteger2)
    {
        // Fails on Win10 FYI x64 Release (AMD RX 550). http://anglebug.com/3760
        ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsDesktopOpenGL());
    
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_border_clamp"));
    
        setUpProgram();
    
        uploadTexture();
    
        GLSampler sampler;
        glBindSampler(0, sampler);
        glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
        glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
        glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        constexpr GLint borderColor[4] = {-50, -50, -50, -50};
        glSamplerParameterIivOES(sampler, GL_TEXTURE_BORDER_COLOR, borderColor);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green);
    }
    
    // Test if the unsigned integer values set as GL_TEXTURE_BORDER_COLOR is used when sampling outside
    // of the unsigned integer texture in GL_CLAMP_TO_BORDER wrap mode (set with glTexParameterIuivOES).
    TEST_P(TextureBorderClampIntegerTestES3, TextureBorderClampIntegerUnsigned)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_border_clamp"));
    
        isUnsignedIntTest = true;
    
        setUpProgram();
    
        uploadTexture();
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        constexpr GLuint borderColor[4] = {150, 150, 150, 150};
        glTexParameterIuivOES(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green);
    }
    
    // Test if the unsigned integer values set as GL_TEXTURE_BORDER_COLOR is used when sampling outside
    // of the unsigned integer texture in GL_CLAMP_TO_BORDER wrap mode (set with
    // glSamplerParameterIuivOES).
    TEST_P(TextureBorderClampIntegerTestES3, TextureBorderClampIntegerUnsigned2)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_border_clamp"));
    
        isUnsignedIntTest = true;
    
        setUpProgram();
    
        uploadTexture();
    
        GLSampler sampler;
        glBindSampler(0, sampler);
        glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
        glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
        glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        constexpr GLuint borderColor[4] = {150, 150, 150, 150};
        glSamplerParameterIuivOES(sampler, GL_TEXTURE_BORDER_COLOR, borderColor);
    
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green);
    }
    
    // ~GL_OES_texture_border_clamp
    
    class TextureLimitsTest : public ANGLETest
    {
      protected:
        struct RGBA8
        {
            uint8_t R, G, B, A;
        };
    
        TextureLimitsTest()
            : mProgram(0), mMaxVertexTextures(0), mMaxFragmentTextures(0), mMaxCombinedTextures(0)
        {
            setWindowWidth(128);
            setWindowHeight(128);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
        }
    
        void testSetUp() override
        {
            glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mMaxVertexTextures);
            glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mMaxFragmentTextures);
            glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mMaxCombinedTextures);
    
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override
        {
            if (mProgram != 0)
            {
                glDeleteProgram(mProgram);
                mProgram = 0;
    
                if (!mTextures.empty())
                {
                    glDeleteTextures(static_cast<GLsizei>(mTextures.size()), &mTextures[0]);
                }
            }
        }
    
        void compileProgramWithTextureCounts(const std::string &vertexPrefix,
                                             GLint vertexTextureCount,
                                             GLint vertexActiveTextureCount,
                                             const std::string &fragPrefix,
                                             GLint fragmentTextureCount,
                                             GLint fragmentActiveTextureCount)
        {
            std::stringstream vertexShaderStr;
            vertexShaderStr << "attribute vec2 position;\n"
                            << "varying vec4 color;\n"
                            << "varying vec2 texCoord;\n";
    
            for (GLint textureIndex = 0; textureIndex < vertexTextureCount; ++textureIndex)
            {
                vertexShaderStr << "uniform sampler2D " << vertexPrefix << textureIndex << ";\n";
            }
    
            vertexShaderStr << "void main() {\n"
                            << "  gl_Position = vec4(position, 0, 1);\n"
                            << "  texCoord = (position * 0.5) + 0.5;\n"
                            << "  color = vec4(0);\n";
    
            for (GLint textureIndex = 0; textureIndex < vertexActiveTextureCount; ++textureIndex)
            {
                vertexShaderStr << "  color += texture2D(" << vertexPrefix << textureIndex
                                << ", texCoord);\n";
            }
    
            vertexShaderStr << "}";
    
            std::stringstream fragmentShaderStr;
            fragmentShaderStr << "varying mediump vec4 color;\n"
                              << "varying mediump vec2 texCoord;\n";
    
            for (GLint textureIndex = 0; textureIndex < fragmentTextureCount; ++textureIndex)
            {
                fragmentShaderStr << "uniform sampler2D " << fragPrefix << textureIndex << ";\n";
            }
    
            fragmentShaderStr << "void main() {\n"
                              << "  gl_FragColor = color;\n";
    
            for (GLint textureIndex = 0; textureIndex < fragmentActiveTextureCount; ++textureIndex)
            {
                fragmentShaderStr << "  gl_FragColor += texture2D(" << fragPrefix << textureIndex
                                  << ", texCoord);\n";
            }
    
            fragmentShaderStr << "}";
    
            const std::string &vertexShaderSource   = vertexShaderStr.str();
            const std::string &fragmentShaderSource = fragmentShaderStr.str();
    
            mProgram = CompileProgram(vertexShaderSource.c_str(), fragmentShaderSource.c_str());
        }
    
        RGBA8 getPixel(GLint texIndex)
        {
            RGBA8 pixel = {static_cast<uint8_t>(texIndex & 0x7u), static_cast<uint8_t>(texIndex >> 3),
                           0, 255u};
            return pixel;
        }
    
        void initTextures(GLint tex2DCount, GLint texCubeCount)
        {
            GLint totalCount = tex2DCount + texCubeCount;
            mTextures.assign(totalCount, 0);
            glGenTextures(totalCount, &mTextures[0]);
            ASSERT_GL_NO_ERROR();
    
            std::vector<RGBA8> texData(16 * 16);
    
            GLint texIndex = 0;
            for (; texIndex < tex2DCount; ++texIndex)
            {
                texData.assign(texData.size(), getPixel(texIndex));
                glActiveTexture(GL_TEXTURE0 + texIndex);
                glBindTexture(GL_TEXTURE_2D, mTextures[texIndex]);
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                             &texData[0]);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            }
    
            ASSERT_GL_NO_ERROR();
    
            for (; texIndex < texCubeCount; ++texIndex)
            {
                texData.assign(texData.size(), getPixel(texIndex));
                glActiveTexture(GL_TEXTURE0 + texIndex);
                glBindTexture(GL_TEXTURE_CUBE_MAP, mTextures[texIndex]);
                glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
                             GL_UNSIGNED_BYTE, &texData[0]);
                glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
                             GL_UNSIGNED_BYTE, &texData[0]);
                glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
                             GL_UNSIGNED_BYTE, &texData[0]);
                glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
                             GL_UNSIGNED_BYTE, &texData[0]);
                glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
                             GL_UNSIGNED_BYTE, &texData[0]);
                glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA, 16, 16, 0, GL_RGBA,
                             GL_UNSIGNED_BYTE, &texData[0]);
                glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            }
    
            ASSERT_GL_NO_ERROR();
        }
    
        void testWithTextures(GLint vertexTextureCount,
                              const std::string &vertexTexturePrefix,
                              GLint fragmentTextureCount,
                              const std::string &fragmentTexturePrefix)
        {
            // Generate textures
            initTextures(vertexTextureCount + fragmentTextureCount, 0);
    
            glUseProgram(mProgram);
            RGBA8 expectedSum = {0};
            for (GLint texIndex = 0; texIndex < vertexTextureCount; ++texIndex)
            {
                std::stringstream uniformNameStr;
                uniformNameStr << vertexTexturePrefix << texIndex;
                const std::string &uniformName = uniformNameStr.str();
                GLint location                 = glGetUniformLocation(mProgram, uniformName.c_str());
                ASSERT_NE(-1, location);
    
                glUniform1i(location, texIndex);
                RGBA8 contribution = getPixel(texIndex);
                expectedSum.R += contribution.R;
                expectedSum.G += contribution.G;
            }
    
            for (GLint texIndex = 0; texIndex < fragmentTextureCount; ++texIndex)
            {
                std::stringstream uniformNameStr;
                uniformNameStr << fragmentTexturePrefix << texIndex;
                const std::string &uniformName = uniformNameStr.str();
                GLint location                 = glGetUniformLocation(mProgram, uniformName.c_str());
                ASSERT_NE(-1, location);
    
                glUniform1i(location, texIndex + vertexTextureCount);
                RGBA8 contribution = getPixel(texIndex + vertexTextureCount);
                expectedSum.R += contribution.R;
                expectedSum.G += contribution.G;
            }
    
            ASSERT_GE(256u, expectedSum.G);
    
            drawQuad(mProgram, "position", 0.5f);
            ASSERT_GL_NO_ERROR();
            EXPECT_PIXEL_EQ(0, 0, expectedSum.R, expectedSum.G, 0, 255);
        }
    
        GLuint mProgram;
        std::vector<GLuint> mTextures;
        GLint mMaxVertexTextures;
        GLint mMaxFragmentTextures;
        GLint mMaxCombinedTextures;
    };
    
    // Test rendering with the maximum vertex texture units.
    TEST_P(TextureLimitsTest, MaxVertexTextures)
    {
        compileProgramWithTextureCounts("tex", mMaxVertexTextures, mMaxVertexTextures, "tex", 0, 0);
        ASSERT_NE(0u, mProgram);
        ASSERT_GL_NO_ERROR();
    
        testWithTextures(mMaxVertexTextures, "tex", 0, "tex");
    }
    
    // Test rendering with the maximum fragment texture units.
    TEST_P(TextureLimitsTest, MaxFragmentTextures)
    {
        compileProgramWithTextureCounts("tex", 0, 0, "tex", mMaxFragmentTextures, mMaxFragmentTextures);
        ASSERT_NE(0u, mProgram);
        ASSERT_GL_NO_ERROR();
    
        testWithTextures(mMaxFragmentTextures, "tex", 0, "tex");
    }
    
    // Test rendering with maximum combined texture units.
    TEST_P(TextureLimitsTest, MaxCombinedTextures)
    {
        // TODO(timvp): http://anglebug.com/3570
        // max per-stage sampled image bindings count (32) exceeds device
        // maxPerStageDescriptorSampledImages limit (16)
        ANGLE_SKIP_TEST_IF(isSwiftshader());
    
        GLint vertexTextures = mMaxVertexTextures;
    
        if (vertexTextures + mMaxFragmentTextures > mMaxCombinedTextures)
        {
            vertexTextures = mMaxCombinedTextures - mMaxFragmentTextures;
        }
    
        compileProgramWithTextureCounts("vtex", vertexTextures, vertexTextures, "ftex",
                                        mMaxFragmentTextures, mMaxFragmentTextures);
        ASSERT_NE(0u, mProgram);
        ASSERT_GL_NO_ERROR();
    
        testWithTextures(vertexTextures, "vtex", mMaxFragmentTextures, "ftex");
    }
    
    // Negative test for exceeding the number of vertex textures
    TEST_P(TextureLimitsTest, ExcessiveVertexTextures)
    {
        compileProgramWithTextureCounts("tex", mMaxVertexTextures + 1, mMaxVertexTextures + 1, "tex", 0,
                                        0);
        ASSERT_EQ(0u, mProgram);
    }
    
    // Negative test for exceeding the number of fragment textures
    TEST_P(TextureLimitsTest, ExcessiveFragmentTextures)
    {
        compileProgramWithTextureCounts("tex", 0, 0, "tex", mMaxFragmentTextures + 1,
                                        mMaxFragmentTextures + 1);
        ASSERT_EQ(0u, mProgram);
    }
    
    // Test active vertex textures under the limit, but excessive textures specified.
    TEST_P(TextureLimitsTest, MaxActiveVertexTextures)
    {
        compileProgramWithTextureCounts("tex", mMaxVertexTextures + 4, mMaxVertexTextures, "tex", 0, 0);
        ASSERT_NE(0u, mProgram);
        ASSERT_GL_NO_ERROR();
    
        testWithTextures(mMaxVertexTextures, "tex", 0, "tex");
    }
    
    // Test active fragment textures under the limit, but excessive textures specified.
    TEST_P(TextureLimitsTest, MaxActiveFragmentTextures)
    {
        compileProgramWithTextureCounts("tex", 0, 0, "tex", mMaxFragmentTextures + 4,
                                        mMaxFragmentTextures);
        ASSERT_NE(0u, mProgram);
        ASSERT_GL_NO_ERROR();
    
        testWithTextures(0, "tex", mMaxFragmentTextures, "tex");
    }
    
    // Negative test for pointing two sampler uniforms of different types to the same texture.
    // GLES 2.0.25 section 2.10.4 page 39.
    TEST_P(TextureLimitsTest, TextureTypeConflict)
    {
        constexpr char kVS[] =
            "attribute vec2 position;\n"
            "varying float color;\n"
            "uniform sampler2D tex2D;\n"
            "uniform samplerCube texCube;\n"
            "void main() {\n"
            "  gl_Position = vec4(position, 0, 1);\n"
            "  vec2 texCoord = (position * 0.5) + 0.5;\n"
            "  color = texture2D(tex2D, texCoord).x;\n"
            "  color += textureCube(texCube, vec3(texCoord, 0)).x;\n"
            "}";
        constexpr char kFS[] =
            "varying mediump float color;\n"
            "void main() {\n"
            "  gl_FragColor = vec4(color, 0, 0, 1);\n"
            "}";
    
        mProgram = CompileProgram(kVS, kFS);
        ASSERT_NE(0u, mProgram);
    
        initTextures(1, 0);
    
        glUseProgram(mProgram);
        GLint tex2DLocation = glGetUniformLocation(mProgram, "tex2D");
        ASSERT_NE(-1, tex2DLocation);
        GLint texCubeLocation = glGetUniformLocation(mProgram, "texCube");
        ASSERT_NE(-1, texCubeLocation);
    
        glUniform1i(tex2DLocation, 0);
        glUniform1i(texCubeLocation, 0);
        ASSERT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    }
    
    class Texture2DNorm16TestES3 : public Texture2DTestES3
    {
      protected:
        Texture2DNorm16TestES3() : Texture2DTestES3(), mTextures{0, 0, 0}, mFBO(0), mRenderbuffer(0) {}
    
        void testSetUp() override
        {
            Texture2DTestES3::testSetUp();
    
            glActiveTexture(GL_TEXTURE0);
            glGenTextures(3, mTextures);
            glGenFramebuffers(1, &mFBO);
            glGenRenderbuffers(1, &mRenderbuffer);
    
            for (size_t textureIndex = 0; textureIndex < 3; textureIndex++)
            {
                glBindTexture(GL_TEXTURE_2D, mTextures[textureIndex]);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            }
    
            glBindTexture(GL_TEXTURE_2D, 0);
    
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override
        {
            glDeleteTextures(3, mTextures);
            glDeleteFramebuffers(1, &mFBO);
            glDeleteRenderbuffers(1, &mRenderbuffer);
    
            Texture2DTestES3::testTearDown();
        }
    
        void testNorm16Texture(GLint internalformat, GLenum format, GLenum type)
        {
            // TODO(http://anglebug.com/4089) Fails on Win Intel OpenGL driver
            ANGLE_SKIP_TEST_IF(IsIntel() && IsOpenGL());
            ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_norm16"));
    
            GLushort pixelValue  = (type == GL_SHORT) ? 0x7FFF : 0x6A35;
            GLushort imageData[] = {pixelValue, pixelValue, pixelValue, pixelValue};
    
            setUpProgram();
    
            glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[0],
                                   0);
    
            glBindTexture(GL_TEXTURE_2D, mTextures[0]);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16_EXT, 1, 1, 0, GL_RGBA, GL_UNSIGNED_SHORT, nullptr);
    
            glBindTexture(GL_TEXTURE_2D, mTextures[1]);
            glTexImage2D(GL_TEXTURE_2D, 0, internalformat, 1, 1, 0, format, type, imageData);
    
            EXPECT_GL_NO_ERROR();
    
            drawQuad(mProgram, "position", 0.5f);
    
            GLubyte expectedValue = (type == GL_SHORT) ? 0xFF : static_cast<GLubyte>(pixelValue >> 8);
    
            EXPECT_PIXEL_COLOR_EQ(0, 0,
                                  SliceFormatColor(format, GLColor(expectedValue, expectedValue,
                                                                   expectedValue, expectedValue)));
    
            glBindFramebuffer(GL_FRAMEBUFFER, 0);
    
            ASSERT_GL_NO_ERROR();
        }
    
        void testReadPixelsRGBAWithRangeAndPixelStoreMode(GLuint x,
                                                          GLuint y,
                                                          GLuint width,
                                                          GLuint height,
                                                          GLint packRowLength,
                                                          GLint packAlignment,
                                                          GLint packSkipPixels,
                                                          GLint packSkipRows,
                                                          GLenum type,
                                                          GLColor16UI color)
        {
            // PACK modes debugging
            GLint s = 2;  // single component size in bytes, UNSIGNED_SHORT -> 2 in our case
            GLint n = 4;  // 4 components per pixel, stands for GL_RGBA
    
            GLuint l       = packRowLength == 0 ? width : packRowLength;
            const GLint &a = packAlignment;
    
            // According to
            // https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glPixelStorei.xhtml
            GLint k                    = (s >= a) ? n * l : a / s * (1 + (s * n * l - 1) / a);
            std::size_t componentCount = n * packSkipPixels + k * (packSkipRows + height);
            if (static_cast<GLuint>(packRowLength) < width)
            {
                componentCount += width * n * s - k;
            }
    
            // Populate the pixels array with random dirty value
            constexpr GLushort kDirtyValue = 0x1234;
            std::vector<GLushort> pixels(componentCount, kDirtyValue);
            glReadPixels(x, y, width, height, GL_RGBA, type, pixels.data());
    
            EXPECT_GL_NO_ERROR();
    
            GLushort *pixelRowStart = pixels.data();
            pixelRowStart += n * packSkipPixels + k * packSkipRows;
    
            std::vector<bool> modifiedPixels(componentCount, false);
    
            char errorInfo[200];
    
            for (GLuint y = 0; y < height; ++y)
            {
                GLushort *curPixel = pixelRowStart;
                for (GLuint x = 0, len = (y == height - 1) ? width : std::min(l, width); x < len; ++x)
                {
                    snprintf(errorInfo, sizeof(errorInfo),
                             "extent: {%u, %u}, coord: (%u, %u), rowLength: %d, alignment: %d, "
                             "skipPixels: %d, skipRows: %d\n",
                             width, height, x, y, packRowLength, packAlignment, packSkipPixels,
                             packSkipRows);
                    EXPECT_EQ(color.R, curPixel[0]) << errorInfo;
                    EXPECT_EQ(color.G, curPixel[1]) << errorInfo;
                    EXPECT_EQ(color.B, curPixel[2]) << errorInfo;
                    EXPECT_EQ(color.A, curPixel[3]) << errorInfo;
    
                    std::ptrdiff_t diff      = curPixel - pixels.data();
                    modifiedPixels[diff + 0] = true;
                    modifiedPixels[diff + 1] = true;
                    modifiedPixels[diff + 2] = true;
                    modifiedPixels[diff + 3] = true;
    
                    curPixel += n;
                }
                pixelRowStart += k;
            }
    
            for (std::size_t i = 0; i < modifiedPixels.size(); ++i)
            {
                if (!modifiedPixels[i])
                {
                    EXPECT_EQ(pixels[i], kDirtyValue);
                }
            }
        }
    
        void testNorm16RenderAndReadPixels(GLint internalformat, GLenum format, GLenum type)
        {
            // TODO(http://anglebug.com/4089) Fails on Win Intel OpenGL driver
            ANGLE_SKIP_TEST_IF(IsIntel() && IsOpenGL());
            // TODO(http://anglebug.com/4245) Fails on Win AMD OpenGL driver
            ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsDesktopOpenGL());
            ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_norm16"));
    
            GLushort pixelValue  = 0x6A35;
            GLushort imageData[] = {pixelValue, pixelValue, pixelValue, pixelValue};
            GLColor16UI color    = SliceFormatColor16UI(
                format, GLColor16UI(pixelValue, pixelValue, pixelValue, pixelValue));
            // Size of drawing viewport
            constexpr GLint width = 8, height = 8;
    
            setUpProgram();
    
            glBindTexture(GL_TEXTURE_2D, mTextures[1]);
            glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, type, nullptr);
    
            glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[1],
                                   0);
    
            glBindTexture(GL_TEXTURE_2D, mTextures[2]);
            glTexImage2D(GL_TEXTURE_2D, 0, internalformat, 1, 1, 0, format, type, imageData);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
            EXPECT_GL_NO_ERROR();
    
            drawQuad(mProgram, "position", 0.5f);
    
            // ReadPixels against different width, height, pixel pack mode combinations to test
            // workaround of pixels rearrangement
    
            // {x, y, width, height}
            std::vector<std::array<GLint, 4>> areas = {
                {0, 0, 1, 1}, {0, 0, 1, 2}, {0, 0, 2, 1}, {0, 0, 2, 2},
                {0, 0, 3, 2}, {0, 0, 3, 3}, {0, 0, 4, 3}, {0, 0, 4, 4},
    
                {1, 3, 3, 2}, {1, 3, 3, 3}, {3, 2, 4, 3}, {3, 2, 4, 4},
    
                {0, 0, 5, 6}, {2, 1, 5, 6}, {0, 0, 6, 1}, {0, 0, 7, 1},
                {0, 0, 7, 3}, {0, 0, 7, 8}, {1, 0, 7, 8}, {0, 0, 8, 8},
            };
    
            // Put default settings at the last
            std::vector<GLint> paramsPackRowLength = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0};
            std::vector<GLint> paramsPackAlignment = {1, 2, 8, 4};
            std::vector<std::array<GLint, 2>> paramsPackSkipPixelsAndRows = {{1, 0}, {0, 1},   {1, 1},
                                                                             {3, 1}, {20, 20}, {0, 0}};
    
            // Restore pixel pack modes later
            GLint restorePackAlignment;
            glGetIntegerv(GL_PACK_ALIGNMENT, &restorePackAlignment);
            GLint restorePackRowLength;
            glGetIntegerv(GL_PACK_ROW_LENGTH, &restorePackRowLength);
            GLint restorePackSkipPixels;
            glGetIntegerv(GL_PACK_SKIP_PIXELS, &restorePackSkipPixels);
            GLint restorePackSkipRows;
            glGetIntegerv(GL_PACK_SKIP_ROWS, &restorePackSkipRows);
    
            // Variable symbols are based on:
            // https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glPixelStorei.xhtml
            for (const auto &skipped : paramsPackSkipPixelsAndRows)
            {
                glPixelStorei(GL_PACK_SKIP_PIXELS, skipped[0]);
                glPixelStorei(GL_PACK_SKIP_ROWS, skipped[1]);
                for (GLint a : paramsPackAlignment)
                {
                    glPixelStorei(GL_PACK_ALIGNMENT, a);
                    for (GLint l : paramsPackRowLength)
                    {
                        glPixelStorei(GL_PACK_ROW_LENGTH, l);
    
                        for (const auto &area : areas)
                        {
                            ASSERT(area[0] + area[2] <= width);
                            ASSERT(area[1] + area[3] <= height);
                            testReadPixelsRGBAWithRangeAndPixelStoreMode(area[0], area[1], area[2],
                                                                         area[3], l, a, skipped[0],
                                                                         skipped[1], type, color);
                        }
                    }
                }
            }
    
            glPixelStorei(GL_PACK_ALIGNMENT, restorePackAlignment);
            glPixelStorei(GL_PACK_ROW_LENGTH, restorePackRowLength);
            glPixelStorei(GL_PACK_SKIP_PIXELS, restorePackSkipPixels);
            glPixelStorei(GL_PACK_SKIP_ROWS, restorePackSkipRows);
    
            glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer);
            glRenderbufferStorage(GL_RENDERBUFFER, internalformat, 1, 1);
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                                      mRenderbuffer);
            glBindRenderbuffer(GL_RENDERBUFFER, 0);
            EXPECT_GL_NO_ERROR();
    
            glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);
    
            EXPECT_PIXEL_16UI_COLOR(
                0, 0, SliceFormatColor16UI(format, GLColor16UI(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF)));
    
            glBindTexture(GL_TEXTURE_2D, mTextures[1]);
            glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 1, 1);
    
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextures[1],
                                   0);
            EXPECT_PIXEL_16UI_COLOR(
                0, 0, SliceFormatColor16UI(format, GLColor16UI(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF)));
    
            ASSERT_GL_NO_ERROR();
    
            glBindFramebuffer(GL_FRAMEBUFFER, 0);
        }
    
        GLuint mTextures[3];
        GLuint mFBO;
        GLuint mRenderbuffer;
    };
    
    TEST_P(Texture2DNorm16TestES3, TextureNorm16R16TextureTest)
    {
        testNorm16Texture(GL_R16_EXT, GL_RED, GL_UNSIGNED_SHORT);
    }
    
    TEST_P(Texture2DNorm16TestES3, TextureNorm16R16SNORMTextureTest)
    {
        testNorm16Texture(GL_R16_SNORM_EXT, GL_RED, GL_SHORT);
    }
    
    TEST_P(Texture2DNorm16TestES3, TextureNorm16RG16TextureTest)
    {
        testNorm16Texture(GL_RG16_EXT, GL_RG, GL_UNSIGNED_SHORT);
    }
    
    TEST_P(Texture2DNorm16TestES3, TextureNorm16RG16SNORMTextureTest)
    {
        testNorm16Texture(GL_RG16_SNORM_EXT, GL_RG, GL_SHORT);
    }
    
    TEST_P(Texture2DNorm16TestES3, TextureNorm16RGB16TextureTest)
    {
        // (http://anglebug.com/4215) Driver bug on some Qualcomm Adreno gpu
        ANGLE_SKIP_TEST_IF(IsNexus5X() && IsOpenGLES());
    
        testNorm16Texture(GL_RGB16_EXT, GL_RGB, GL_UNSIGNED_SHORT);
    }
    
    TEST_P(Texture2DNorm16TestES3, TextureNorm16RGB16SNORMTextureTest)
    {
        // (http://anglebug.com/4215) Driver bug on some Qualcomm Adreno gpu
        ANGLE_SKIP_TEST_IF(IsNexus5X() && IsOpenGLES());
    
        testNorm16Texture(GL_RGB16_SNORM_EXT, GL_RGB, GL_SHORT);
    }
    
    TEST_P(Texture2DNorm16TestES3, TextureNorm16RGBA16TextureTest)
    {
        testNorm16Texture(GL_RGBA16_EXT, GL_RGBA, GL_UNSIGNED_SHORT);
    }
    
    TEST_P(Texture2DNorm16TestES3, TextureNorm16RGBA16SNORMTextureTest)
    {
        testNorm16Texture(GL_RGBA16_SNORM_EXT, GL_RGBA, GL_SHORT);
    }
    
    TEST_P(Texture2DNorm16TestES3, TextureNorm16R16RenderTest)
    {
        // http://anglebug.com/5153
        ANGLE_SKIP_TEST_IF(IsOSX() && IsOpenGL() && IsNVIDIA());
    
        testNorm16RenderAndReadPixels(GL_R16_EXT, GL_RED, GL_UNSIGNED_SHORT);
    }
    
    TEST_P(Texture2DNorm16TestES3, TextureNorm16RG16RenderTest)
    {
        // http://anglebug.com/5153
        ANGLE_SKIP_TEST_IF(IsOSX() && IsOpenGL() && IsNVIDIA());
    
        testNorm16RenderAndReadPixels(GL_RG16_EXT, GL_RG, GL_UNSIGNED_SHORT);
    }
    
    TEST_P(Texture2DNorm16TestES3, TextureNorm16RGBA16RenderTest)
    {
        // http://anglebug.com/5153
        ANGLE_SKIP_TEST_IF(IsOSX() && IsOpenGL() && IsNVIDIA());
    
        testNorm16RenderAndReadPixels(GL_RGBA16_EXT, GL_RGBA, GL_UNSIGNED_SHORT);
    }
    
    class Texture2DRGTest : public Texture2DTest
    {
      protected:
        Texture2DRGTest()
            : Texture2DTest(), mRenderableTexture(0), mTestTexture(0), mFBO(0), mRenderbuffer(0)
        {}
    
        void testSetUp() override
        {
            Texture2DTest::testSetUp();
    
            glActiveTexture(GL_TEXTURE0);
            glGenTextures(1, &mRenderableTexture);
            glGenTextures(1, &mTestTexture);
            glGenFramebuffers(1, &mFBO);
            glGenRenderbuffers(1, &mRenderbuffer);
    
            glBindTexture(GL_TEXTURE_2D, mRenderableTexture);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glBindTexture(GL_TEXTURE_2D, mTestTexture);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
            glBindTexture(GL_TEXTURE_2D, 0);
    
            setUpProgram();
            glUseProgram(mProgram);
            glUniform1i(mTexture2DUniformLocation, 0);
    
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mRenderableTexture);
            glDeleteTextures(1, &mTestTexture);
            glDeleteFramebuffers(1, &mFBO);
            glDeleteRenderbuffers(1, &mRenderbuffer);
    
            Texture2DTest::testTearDown();
        }
    
        void setupFormatTextures(GLenum internalformat, GLenum format, GLenum type, GLvoid *imageData)
        {
            glBindTexture(GL_TEXTURE_2D, mRenderableTexture);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
            glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                   mRenderableTexture, 0);
    
            glBindTexture(GL_TEXTURE_2D, mTestTexture);
            glTexImage2D(GL_TEXTURE_2D, 0, internalformat, 1, 1, 0, format, type, imageData);
    
            EXPECT_GL_NO_ERROR();
        }
    
        void testRGTexture(GLColor expectedColor)
        {
            drawQuad(mProgram, "position", 0.5f);
    
            EXPECT_GL_NO_ERROR();
            EXPECT_PIXEL_COLOR_NEAR(0, 0, expectedColor, kPixelTolerance);
        }
    
        void testRGRender(GLenum internalformat, GLenum format)
        {
            glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer);
            glRenderbufferStorage(GL_RENDERBUFFER, internalformat, 1, 1);
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                                      mRenderbuffer);
            glBindRenderbuffer(GL_RENDERBUFFER, 0);
            EXPECT_GL_NO_ERROR();
    
            glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);
    
            glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, 1, 1);
    
            ASSERT_GL_NO_ERROR();
            EXPECT_PIXEL_COLOR_EQ(0, 0, SliceFormatColor(format, GLColor(255u, 255u, 255u, 255u)));
        }
    
        GLuint mRenderableTexture;
        GLuint mTestTexture;
        GLuint mFBO;
        GLuint mRenderbuffer;
    };
    
    // Test unorm texture formats enabled by the GL_EXT_texture_rg extension.
    TEST_P(Texture2DRGTest, TextureRGUNormTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_rg"));
        // This workaround causes a GL error on Windows AMD, which is likely a driver bug.
        // The workaround is not intended to be enabled in this configuration so skip it.
        ANGLE_SKIP_TEST_IF(GetParam().eglParameters.emulateCopyTexImage2DFromRenderbuffers &&
                           IsWindows() && IsAMD());
    
        GLubyte pixelValue  = 0xab;
        GLubyte imageData[] = {pixelValue, pixelValue};
    
        setupFormatTextures(GL_RED_EXT, GL_RED_EXT, GL_UNSIGNED_BYTE, imageData);
        testRGTexture(
            SliceFormatColor(GL_RED_EXT, GLColor(pixelValue, pixelValue, pixelValue, pixelValue)));
        testRGRender(GL_R8_EXT, GL_RED_EXT);
    
        setupFormatTextures(GL_RG_EXT, GL_RG_EXT, GL_UNSIGNED_BYTE, imageData);
        testRGTexture(
            SliceFormatColor(GL_RG_EXT, GLColor(pixelValue, pixelValue, pixelValue, pixelValue)));
        testRGRender(GL_RG8_EXT, GL_RG_EXT);
    }
    
    // Test float texture formats enabled by the GL_EXT_texture_rg extension.
    TEST_P(Texture2DRGTest, TextureRGFloatTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_rg"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float"));
    
        GLfloat pixelValue  = 0.54321;
        GLfloat imageData[] = {pixelValue, pixelValue};
    
        GLubyte expectedValue = static_cast<GLubyte>(pixelValue * 255.0f);
        GLColor expectedColor = GLColor(expectedValue, expectedValue, expectedValue, expectedValue);
    
        setupFormatTextures(GL_RED_EXT, GL_RED_EXT, GL_FLOAT, imageData);
        testRGTexture(SliceFormatColor(GL_RED_EXT, expectedColor));
    
        setupFormatTextures(GL_RG_EXT, GL_RG_EXT, GL_FLOAT, imageData);
        testRGTexture(SliceFormatColor(GL_RG_EXT, expectedColor));
    }
    
    // Test half-float texture formats enabled by the GL_EXT_texture_rg extension.
    TEST_P(Texture2DRGTest, TextureRGHalfFloatTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_rg"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_half_float"));
    
        GLfloat pixelValueFloat = 0.543f;
        GLhalf pixelValue       = 0x3858;
        GLhalf imageData[]      = {pixelValue, pixelValue};
    
        GLubyte expectedValue = static_cast<GLubyte>(pixelValueFloat * 255.0f);
        GLColor expectedColor = GLColor(expectedValue, expectedValue, expectedValue, expectedValue);
    
        setupFormatTextures(GL_RED_EXT, GL_RED_EXT, GL_HALF_FLOAT_OES, imageData);
        testRGTexture(SliceFormatColor(GL_RED_EXT, expectedColor));
    
        setupFormatTextures(GL_RG_EXT, GL_RG_EXT, GL_HALF_FLOAT_OES, imageData);
        testRGTexture(SliceFormatColor(GL_RG_EXT, expectedColor));
    }
    
    class Texture2DFloatTest : public Texture2DTest
    {
      protected:
        Texture2DFloatTest()
            : Texture2DTest(), mRenderableTexture(0), mTestTexture(0), mFBO(0), mRenderbuffer(0)
        {}
    
        void testSetUp() override
        {
            Texture2DTest::testSetUp();
    
            glActiveTexture(GL_TEXTURE0);
            glGenTextures(1, &mRenderableTexture);
            glGenTextures(1, &mTestTexture);
            glGenFramebuffers(1, &mFBO);
            glGenRenderbuffers(1, &mRenderbuffer);
    
            setUpProgram();
            glUseProgram(mProgram);
            glUniform1i(mTexture2DUniformLocation, 0);
    
            glBindTexture(GL_TEXTURE_2D, mRenderableTexture);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
            glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                   mRenderableTexture, 0);
    
            ASSERT_GL_NO_ERROR();
        }
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mRenderableTexture);
            glDeleteTextures(1, &mTestTexture);
            glDeleteFramebuffers(1, &mFBO);
            glDeleteRenderbuffers(1, &mRenderbuffer);
    
            Texture2DTest::testTearDown();
        }
    
        void testFloatTextureSample(GLenum internalFormat, GLenum format, GLenum type)
        {
            constexpr GLfloat imageDataFloat[] = {
                0.2f,
                0.3f,
                0.4f,
                0.5f,
            };
            constexpr GLhalf imageDataHalf[] = {
                0x3266,
                0x34CD,
                0x3666,
                0x3800,
            };
            GLColor expectedValue;
            for (int i = 0; i < 4; i++)
            {
                expectedValue[i] = static_cast<GLubyte>(imageDataFloat[i] * 255.0f);
            }
    
            const GLvoid *imageData;
            switch (type)
            {
                case GL_FLOAT:
                    imageData = imageDataFloat;
                    break;
                case GL_HALF_FLOAT:
                case GL_HALF_FLOAT_OES:
                    imageData = imageDataHalf;
                    break;
                default:
                    imageData = nullptr;
            }
            ASSERT(imageData != nullptr);
    
            glBindTexture(GL_TEXTURE_2D, mTestTexture);
            glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0, format, type, imageData);
    
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
            glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
            drawQuad(mProgram, "position", 0.5f);
    
            EXPECT_GL_NO_ERROR();
            EXPECT_PIXEL_COLOR_NEAR(0, 0, SliceFormatColor(format, expectedValue), kPixelTolerance);
        }
    
        void testFloatTextureLinear(GLenum internalFormat, GLenum format, GLenum type)
        {
            int numComponents;
            switch (format)
            {
                case GL_RGBA:
                    numComponents = 4;
                    break;
                case GL_RGB:
                    numComponents = 3;
                    break;
                case GL_LUMINANCE_ALPHA:
                    numComponents = 2;
                    break;
                case GL_LUMINANCE:
                case GL_ALPHA:
                    numComponents = 1;
                    break;
                default:
                    numComponents = 0;
            }
            ASSERT(numComponents > 0);
    
            constexpr GLfloat pixelIntensitiesFloat[] = {0.0f, 1.0f, 0.0f, 1.0f};
            constexpr GLhalf pixelIntensitiesHalf[]   = {0x0000, 0x3C00, 0x0000, 0x3C00};
    
            GLfloat imageDataFloat[16];
            GLhalf imageDataHalf[16];
            for (int i = 0; i < 4; i++)
            {
                for (int c = 0; c < numComponents; c++)
                {
                    imageDataFloat[i * numComponents + c] = pixelIntensitiesFloat[i];
                    imageDataHalf[i * numComponents + c]  = pixelIntensitiesHalf[i];
                }
            }
    
            const GLvoid *imageData;
            switch (type)
            {
                case GL_FLOAT:
                    imageData = imageDataFloat;
                    break;
                case GL_HALF_FLOAT:
                case GL_HALF_FLOAT_OES:
                    imageData = imageDataHalf;
                    break;
                default:
                    imageData = nullptr;
            }
            ASSERT(imageData != nullptr);
    
            glBindTexture(GL_TEXTURE_2D, mTestTexture);
            glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, 2, 2, 0, format, type, imageData);
    
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
            glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
            drawQuad(mProgram, "position", 0.5f);
    
            EXPECT_GL_NO_ERROR();
            // Source texture contains 2 black pixels and 2 white pixels, we sample in the center so we
            // should expect the final value to be gray (halfway in-between)
            EXPECT_PIXEL_COLOR_NEAR(0, 0, SliceFormatColor(format, GLColor(127u, 127u, 127u, 127u)),
                                    kPixelTolerance);
        }
    
        bool performFloatTextureRender(GLenum internalFormat,
                                       GLenum renderBufferFormat,
                                       GLenum format,
                                       GLenum type)
        {
            glBindTexture(GL_TEXTURE_2D, mTestTexture);
            glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0, format, type, nullptr);
            glBindTexture(GL_TEXTURE_2D, 0);
    
            glBindRenderbuffer(GL_RENDERBUFFER, mRenderbuffer);
            glRenderbufferStorage(GL_RENDERBUFFER, renderBufferFormat, 1, 1);
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                                      mRenderbuffer);
            glBindRenderbuffer(GL_RENDERBUFFER, 0);
            EXPECT_GL_NO_ERROR();
    
            if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            {
                return false;
            }
    
            glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
    
            glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);
    
            EXPECT_GL_NO_ERROR();
            return true;
        }
    
        GLuint mRenderableTexture;
        GLuint mTestTexture;
        GLuint mFBO;
        GLuint mRenderbuffer;
    };
    
    class Texture2DFloatTestES3 : public Texture2DFloatTest
    {
      protected:
        void testFloatTextureRender(GLenum internalFormat, GLenum format, GLenum type)
        {
            bool framebufferComplete =
                performFloatTextureRender(internalFormat, internalFormat, format, type);
            EXPECT_TRUE(framebufferComplete);
            EXPECT_PIXEL_COLOR32F_NEAR(0, 0,
                                       SliceFormatColor32F(format, GLColor32F(1.0f, 1.0f, 1.0f, 1.0f)),
                                       kPixelTolerance32F);
        }
    };
    
    class Texture2DFloatTestES2 : public Texture2DFloatTest
    {
      protected:
        bool checkFloatTextureRender(GLenum renderBufferFormat, GLenum format, GLenum type)
        {
            bool framebufferComplete =
                performFloatTextureRender(format, renderBufferFormat, format, type);
    
            if (!framebufferComplete)
            {
                return false;
            }
    
            EXPECT_PIXEL_COLOR32F_NEAR(0, 0,
                                       SliceFormatColor32F(format, GLColor32F(1.0f, 1.0f, 1.0f, 1.0f)),
                                       kPixelTolerance32F);
            return true;
        }
    };
    
    // Test texture sampling for ES3 float texture formats
    TEST_P(Texture2DFloatTestES3, TextureFloatSampleBasicTest)
    {
        testFloatTextureSample(GL_RGBA32F, GL_RGBA, GL_FLOAT);
        testFloatTextureSample(GL_RGB32F, GL_RGB, GL_FLOAT);
    }
    
    // Test texture sampling for ES2 float texture formats
    TEST_P(Texture2DFloatTestES2, TextureFloatSampleBasicTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float"));
        testFloatTextureSample(GL_RGBA, GL_RGBA, GL_FLOAT);
        testFloatTextureSample(GL_RGB, GL_RGB, GL_FLOAT);
    }
    
    // Test texture sampling for ES3 half float texture formats
    TEST_P(Texture2DFloatTestES3, TextureHalfFloatSampleBasicTest)
    {
        testFloatTextureSample(GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT);
        testFloatTextureSample(GL_RGB16F, GL_RGB, GL_HALF_FLOAT);
    }
    
    // Test texture sampling for ES2 half float texture formats
    TEST_P(Texture2DFloatTestES2, TextureHalfFloatSampleBasicTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_half_float"));
        testFloatTextureSample(GL_RGBA, GL_RGBA, GL_HALF_FLOAT_OES);
        testFloatTextureSample(GL_RGB, GL_RGB, GL_HALF_FLOAT_OES);
    }
    
    // Test texture sampling for legacy GLES 2.0 float texture formats in ES3
    TEST_P(Texture2DFloatTestES3, TextureFloatSampleLegacyTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float"));
    
        testFloatTextureSample(GL_LUMINANCE, GL_LUMINANCE, GL_FLOAT);
        testFloatTextureSample(GL_ALPHA, GL_ALPHA, GL_FLOAT);
        testFloatTextureSample(GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_FLOAT);
    
        if (IsGLExtensionEnabled("GL_EXT_texture_storage"))
        {
            testFloatTextureSample(GL_LUMINANCE32F_EXT, GL_LUMINANCE, GL_FLOAT);
            testFloatTextureSample(GL_ALPHA32F_EXT, GL_ALPHA, GL_FLOAT);
            testFloatTextureSample(GL_LUMINANCE_ALPHA32F_EXT, GL_LUMINANCE_ALPHA, GL_FLOAT);
        }
    }
    
    // Test texture sampling for legacy GLES 2.0 float texture formats in ES2
    TEST_P(Texture2DFloatTestES2, TextureFloatSampleLegacyTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float"));
    
        testFloatTextureSample(GL_LUMINANCE, GL_LUMINANCE, GL_FLOAT);
        testFloatTextureSample(GL_ALPHA, GL_ALPHA, GL_FLOAT);
        testFloatTextureSample(GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_FLOAT);
    }
    
    // Test texture sampling for legacy GLES 2.0 half float texture formats in ES3
    TEST_P(Texture2DFloatTestES3, TextureHalfFloatSampleLegacyTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_half_float"));
    
        testFloatTextureSample(GL_LUMINANCE, GL_LUMINANCE, GL_HALF_FLOAT_OES);
        testFloatTextureSample(GL_ALPHA, GL_ALPHA, GL_HALF_FLOAT_OES);
        testFloatTextureSample(GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_HALF_FLOAT_OES);
    
        if (IsGLExtensionEnabled("GL_EXT_texture_storage"))
        {
            testFloatTextureSample(GL_LUMINANCE16F_EXT, GL_LUMINANCE, GL_HALF_FLOAT);
            testFloatTextureSample(GL_ALPHA16F_EXT, GL_ALPHA, GL_HALF_FLOAT);
            testFloatTextureSample(GL_LUMINANCE_ALPHA16F_EXT, GL_LUMINANCE_ALPHA, GL_HALF_FLOAT);
        }
    }
    // Test texture sampling for legacy GLES 2.0 half float texture formats in ES2
    TEST_P(Texture2DFloatTestES2, TextureHalfFloatSampleLegacyTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_half_float"));
    
        testFloatTextureSample(GL_LUMINANCE, GL_LUMINANCE, GL_HALF_FLOAT_OES);
        testFloatTextureSample(GL_ALPHA, GL_ALPHA, GL_HALF_FLOAT_OES);
        testFloatTextureSample(GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_HALF_FLOAT_OES);
    }
    
    // Test linear sampling for ES3 32F formats
    TEST_P(Texture2DFloatTestES3, TextureFloatLinearTest)
    {
        // TODO(anglebug.com/5360): Failing on ARM-based Apple DTKs.
        ANGLE_SKIP_TEST_IF(IsOSX() && IsARM64() && (IsDesktopOpenGL() || IsMetal()));
    
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float_linear"));
    
        testFloatTextureLinear(GL_RGBA32F, GL_RGBA, GL_FLOAT);
        testFloatTextureLinear(GL_RGB32F, GL_RGB, GL_FLOAT);
    }
    // Test linear sampling for ES2 32F formats
    TEST_P(Texture2DFloatTestES2, TextureFloatLinearTest)
    {
        // TODO(anglebug.com/5360): Failing on ARM-based Apple DTKs.
        ANGLE_SKIP_TEST_IF(IsOSX() && IsARM64() && (IsDesktopOpenGL() || IsMetal()));
    
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float_linear"));
    
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float"));
    
        testFloatTextureLinear(GL_RGBA, GL_RGBA, GL_FLOAT);
    }
    
    // Test linear sampling for ES3 16F formats
    TEST_P(Texture2DFloatTestES3, TextureHalfFloatLinearTest)
    {
        // Half float formats must be linearly filterable in GLES 3.0 core
        testFloatTextureLinear(GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT);
        testFloatTextureLinear(GL_RGB16F, GL_RGB, GL_HALF_FLOAT);
    }
    // Test linear sampling for ES2 16F formats
    TEST_P(Texture2DFloatTestES2, TextureHalfFloatLinearTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_half_float_linear"));
        testFloatTextureLinear(GL_RGBA, GL_RGBA, GL_HALF_FLOAT_OES);
        testFloatTextureLinear(GL_RGB, GL_RGB, GL_HALF_FLOAT_OES);
    }
    
    // Test linear sampling for legacy GLES 2.0 32F formats in ES3
    TEST_P(Texture2DFloatTestES3, TextureFloatLinearLegacyTest)
    {
        // TODO(anglebug.com/5360): Failing on ARM-based Apple DTKs.
        ANGLE_SKIP_TEST_IF(IsOSX() && IsARM64() && (IsDesktopOpenGL() || IsMetal()));
    
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float_linear"));
    
        testFloatTextureLinear(GL_LUMINANCE, GL_LUMINANCE, GL_FLOAT);
        testFloatTextureLinear(GL_ALPHA, GL_ALPHA, GL_FLOAT);
        testFloatTextureLinear(GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_FLOAT);
    
        if (IsGLExtensionEnabled("GL_EXT_texture_storage"))
        {
            testFloatTextureLinear(GL_LUMINANCE32F_EXT, GL_LUMINANCE, GL_FLOAT);
            testFloatTextureLinear(GL_ALPHA32F_EXT, GL_ALPHA, GL_FLOAT);
            testFloatTextureLinear(GL_LUMINANCE_ALPHA32F_EXT, GL_LUMINANCE_ALPHA, GL_FLOAT);
        }
    }
    // Test linear sampling for legacy GLES 2.0 32F formats in ES2
    TEST_P(Texture2DFloatTestES2, TextureFloatLinearLegacyTest)
    {
        // TODO(anglebug.com/5360): Failing on ARM-based Apple DTKs.
        ANGLE_SKIP_TEST_IF(IsOSX() && IsARM64() && (IsDesktopOpenGL() || IsMetal()));
    
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_float_linear"));
    
        testFloatTextureLinear(GL_LUMINANCE, GL_LUMINANCE, GL_FLOAT);
        testFloatTextureLinear(GL_ALPHA, GL_ALPHA, GL_FLOAT);
        testFloatTextureLinear(GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_FLOAT);
    }
    
    // Test linear sampling for legacy GLES 2.0 16F formats in ES3
    TEST_P(Texture2DFloatTestES3, TextureHalfFloatLinearLegacyTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_half_float"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_half_float_linear"));
    
        testFloatTextureLinear(GL_LUMINANCE, GL_LUMINANCE, GL_HALF_FLOAT_OES);
        testFloatTextureLinear(GL_ALPHA, GL_ALPHA, GL_HALF_FLOAT_OES);
        testFloatTextureLinear(GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_HALF_FLOAT_OES);
    
        if (IsGLExtensionEnabled("GL_EXT_texture_storage"))
        {
            testFloatTextureLinear(GL_LUMINANCE16F_EXT, GL_LUMINANCE, GL_HALF_FLOAT);
            testFloatTextureLinear(GL_ALPHA16F_EXT, GL_ALPHA, GL_HALF_FLOAT);
            testFloatTextureLinear(GL_LUMINANCE_ALPHA16F_EXT, GL_LUMINANCE_ALPHA, GL_HALF_FLOAT);
        }
    }
    // Test linear sampling for legacy GLES 2.0 16F formats in ES2
    TEST_P(Texture2DFloatTestES2, TextureHalfFloatLinearLegacyTest)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_half_float"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_half_float_linear"));
    
        testFloatTextureLinear(GL_LUMINANCE, GL_LUMINANCE, GL_HALF_FLOAT_OES);
        testFloatTextureLinear(GL_ALPHA, GL_ALPHA, GL_HALF_FLOAT_OES);
        testFloatTextureLinear(GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_HALF_FLOAT_OES);
    }
    
    // Test color-renderability for ES3 float and half float textures
    TEST_P(Texture2DFloatTestES3, TextureFloatRenderTest)
    {
        // http://anglebug.com/4092
        ANGLE_SKIP_TEST_IF(IsD3D9());
        // EXT_color_buffer_float covers float, half float, and 11-11-10 float formats
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_color_buffer_float"));
    
        testFloatTextureRender(GL_R32F, GL_RED, GL_FLOAT);
        testFloatTextureRender(GL_RG32F, GL_RG, GL_FLOAT);
        testFloatTextureRender(GL_RGBA32F, GL_RGBA, GL_FLOAT);
    
        testFloatTextureRender(GL_R16F, GL_RED, GL_HALF_FLOAT);
        testFloatTextureRender(GL_RG16F, GL_RG, GL_HALF_FLOAT);
        testFloatTextureRender(GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT);
    
        testFloatTextureRender(GL_R11F_G11F_B10F, GL_RGB, GL_FLOAT);
    }
    
    // Test color-renderability for ES2 half float textures
    TEST_P(Texture2DFloatTestES2, TextureFloatRenderTest)
    {
        // EXT_color_buffer_half_float requires at least one format to be renderable, but does not
        // require a specific one
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_color_buffer_half_float"));
        // https://crbug.com/1003971
        ANGLE_SKIP_TEST_IF(IsOzone());
        // http://anglebug.com/4092
        ANGLE_SKIP_TEST_IF(IsD3D9());
    
        bool atLeastOneSupported = false;
    
        if (IsGLExtensionEnabled("GL_OES_texture_half_float") ||
            IsGLExtensionEnabled("GL_OES_texture_half_float"))
        {
            atLeastOneSupported |= checkFloatTextureRender(GL_R16F_EXT, GL_RED_EXT, GL_HALF_FLOAT_OES);
            atLeastOneSupported |= checkFloatTextureRender(GL_RG16F_EXT, GL_RG_EXT, GL_HALF_FLOAT_OES);
        }
        if (IsGLExtensionEnabled("GL_OES_texture_half_float"))
        {
            atLeastOneSupported |= checkFloatTextureRender(GL_RGB16F_EXT, GL_RGB, GL_HALF_FLOAT_OES);
    
            // If OES_texture_half_float is supported, then RGBA half float textures must be renderable
            bool rgbaSupported = checkFloatTextureRender(GL_RGBA16F_EXT, GL_RGBA, GL_HALF_FLOAT_OES);
            EXPECT_TRUE(rgbaSupported);
            atLeastOneSupported |= rgbaSupported;
        }
    
        EXPECT_TRUE(atLeastOneSupported);
    }
    
    // Test that UNPACK_SKIP_IMAGES doesn't have an effect on 2D texture uploads.
    // GLES 3.0.4 section 3.8.3.
    TEST_P(Texture2DTestES3, UnpackSkipImages2D)
    {
        // Crashes on Nexus 5X due to a driver bug. http://anglebug.com/1429
        ANGLE_SKIP_TEST_IF(IsNexus5X() && IsOpenGLES());
    
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        ASSERT_GL_NO_ERROR();
    
        // SKIP_IMAGES should not have an effect on uploading 2D textures
        glPixelStorei(GL_UNPACK_SKIP_IMAGES, 1000);
        ASSERT_GL_NO_ERROR();
    
        std::vector<GLColor> pixelsGreen(128u * 128u, GLColor::green);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     pixelsGreen.data());
        ASSERT_GL_NO_ERROR();
    
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 128, 128, GL_RGBA, GL_UNSIGNED_BYTE,
                        pixelsGreen.data());
        ASSERT_GL_NO_ERROR();
    
        glUseProgram(mProgram);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Test that skip defined in unpack parameters is taken into account when determining whether
    // unpacking source extends outside unpack buffer bounds.
    TEST_P(Texture2DTestES3, UnpackSkipPixelsOutOfBounds)
    {
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        ASSERT_GL_NO_ERROR();
    
        GLBuffer buf;
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf.get());
        std::vector<GLColor> pixelsGreen(128u * 128u, GLColor::green);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, pixelsGreen.size() * 4u, pixelsGreen.data(),
                     GL_DYNAMIC_COPY);
        ASSERT_GL_NO_ERROR();
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 128, 128, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        ASSERT_GL_NO_ERROR();
    
        glPixelStorei(GL_UNPACK_SKIP_PIXELS, 1);
        ASSERT_GL_NO_ERROR();
    
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 128, 128, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    
        glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
        glPixelStorei(GL_UNPACK_SKIP_ROWS, 1);
        ASSERT_GL_NO_ERROR();
    
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 128, 128, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    }
    
    // Test that unpacking rows that overlap in a pixel unpack buffer works as expected.
    TEST_P(Texture2DTestES3, UnpackOverlappingRowsFromUnpackBuffer)
    {
        ANGLE_SKIP_TEST_IF(IsD3D11());
    
        // Incorrect rendering results seen on OSX AMD.
        ANGLE_SKIP_TEST_IF(IsOpenGL() && IsOSX() && IsAMD());
    
        const GLuint width            = 8u;
        const GLuint height           = 8u;
        const GLuint unpackRowLength  = 5u;
        const GLuint unpackSkipPixels = 1u;
    
        setWindowWidth(width);
        setWindowHeight(height);
    
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        ASSERT_GL_NO_ERROR();
    
        GLBuffer buf;
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf.get());
        std::vector<GLColor> pixelsGreen((height - 1u) * unpackRowLength + width + unpackSkipPixels,
                                         GLColor::green);
    
        for (GLuint skippedPixel = 0u; skippedPixel < unpackSkipPixels; ++skippedPixel)
        {
            pixelsGreen[skippedPixel] = GLColor(255, 0, 0, 255);
        }
    
        glBufferData(GL_PIXEL_UNPACK_BUFFER, pixelsGreen.size() * 4u, pixelsGreen.data(),
                     GL_DYNAMIC_COPY);
        ASSERT_GL_NO_ERROR();
    
        glPixelStorei(GL_UNPACK_ROW_LENGTH, unpackRowLength);
        glPixelStorei(GL_UNPACK_SKIP_PIXELS, unpackSkipPixels);
        ASSERT_GL_NO_ERROR();
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
        ASSERT_GL_NO_ERROR();
    
        glUseProgram(mProgram);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
    
        GLuint windowPixelCount = getWindowWidth() * getWindowHeight();
        std::vector<GLColor> actual(windowPixelCount, GLColor::black);
        glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                     actual.data());
        std::vector<GLColor> expected(windowPixelCount, GLColor::green);
        EXPECT_EQ(expected, actual);
    }
    
    template <typename T>
    T UNorm(double value)
    {
        return static_cast<T>(value * static_cast<double>(std::numeric_limits<T>::max()));
    }
    
    // Test rendering a depth texture with mipmaps.
    TEST_P(Texture2DTestES3, DepthTexturesWithMipmaps)
    {
        // TODO(cwallez) this is failing on Intel Win7 OpenGL.
        // TODO(zmo) this is faling on Win Intel HD 530 Debug.
        // http://anglebug.com/1706
        ANGLE_SKIP_TEST_IF(IsIntel() && IsWindows() && IsOpenGL());
    
        // Seems to fail on AMD D3D11. Possibly driver bug. http://anglebug.com/3342
        ANGLE_SKIP_TEST_IF(IsAMD() && IsWindows() && IsD3D11());
    
        // TODO(cnorthrop): Also failing on Vulkan/Windows/AMD. http://anglebug.com/3950
        ANGLE_SKIP_TEST_IF(IsAMD() && IsWindows() && IsVulkan());
    
        const int size = getWindowWidth();
    
        auto dim   = [size](int level) { return size >> level; };
        int levels = gl::log2(size);
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexStorage2D(GL_TEXTURE_2D, levels, GL_DEPTH_COMPONENT24, size, size);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        ASSERT_GL_NO_ERROR();
    
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
    
        std::vector<unsigned char> expected;
    
        for (int level = 0; level < levels; ++level)
        {
            double value = (static_cast<double>(level) / static_cast<double>(levels - 1));
            expected.push_back(UNorm<unsigned char>(value));
    
            int levelDim = dim(level);
    
            ASSERT_GT(levelDim, 0);
    
            std::vector<unsigned int> initData(levelDim * levelDim, UNorm<unsigned int>(value));
            glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, levelDim, levelDim, GL_DEPTH_COMPONENT,
                            GL_UNSIGNED_INT, initData.data());
        }
        ASSERT_GL_NO_ERROR();
    
        for (int level = 0; level < levels; ++level)
        {
            glViewport(0, 0, dim(level), dim(level));
            drawQuad(mProgram, "position", 0.5f);
            GLColor actual = ReadColor(0, 0);
            EXPECT_NEAR(expected[level], actual.R, 10u);
        }
    
        ASSERT_GL_NO_ERROR();
    }
    
    class Texture2DDepthTest : public Texture2DTest
    {
      protected:
        Texture2DDepthTest() : Texture2DTest() {}
    
        const char *getVertexShaderSource() override
        {
            return "attribute vec4 vPosition;\n"
                   "void main() {\n"
                   "  gl_Position = vPosition;\n"
                   "}\n";
        }
    
        const char *getFragmentShaderSource() override
        {
            return "precision mediump float;\n"
                   "uniform sampler2D ShadowMap;"
                   "void main() {\n"
                   "  vec4 shadow_value = texture2D(ShadowMap, vec2(0.5, 0.5));"
                   "  if (shadow_value.x == shadow_value.z && shadow_value.x != 0.0) {"
                   "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);"
                   "  } else {"
                   "    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
                   "  }"
                   "}\n";
        }
    
        bool checkTexImageFormatSupport(GLenum format, GLenum internalformat, GLenum type)
        {
            EXPECT_GL_NO_ERROR();
    
            GLTexture tex;
            glBindTexture(GL_TEXTURE_2D, tex);
            glTexImage2D(GL_TEXTURE_2D, 0, format, 1, 1, 0, format, type, nullptr);
    
            return (glGetError() == GL_NO_ERROR);
        }
    
        void testBehavior(bool useSizedComponent)
        {
            int w                 = getWindowWidth();
            int h                 = getWindowHeight();
            GLuint format         = GL_DEPTH_COMPONENT;
            GLuint internalFormat = GL_DEPTH_COMPONENT;
    
            if (useSizedComponent)
            {
                internalFormat = GL_DEPTH_COMPONENT24;
            }
    
            GLFramebuffer fbo;
            glBindFramebuffer(GL_FRAMEBUFFER, fbo);
            ASSERT_GL_NO_ERROR();
    
            GLTexture depthTexture;
            glBindTexture(GL_TEXTURE_2D, depthTexture);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
            TexCoordDrawTest::setUpProgram();
            GLint shadowMapLocation = glGetUniformLocation(mProgram, "ShadowMap");
            ASSERT_NE(-1, shadowMapLocation);
    
            GLint positionLocation = glGetAttribLocation(mProgram, "vPosition");
            ASSERT_NE(-1, positionLocation);
    
            ANGLE_SKIP_TEST_IF(!checkTexImageFormatSupport(format, internalFormat, GL_UNSIGNED_INT));
            glBindTexture(GL_TEXTURE_2D, depthTexture);
            glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, GL_UNSIGNED_INT, nullptr);
            ASSERT_GL_NO_ERROR();
    
            // try adding a color buffer.
            GLuint colorTex = 0;
            glGenTextures(1, &colorTex);
            glBindTexture(GL_TEXTURE_2D, colorTex);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
            EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
            ASSERT_GL_NO_ERROR();
    
            glViewport(0, 0, w, h);
            // Fill depthTexture with 0.75
            glClearDepthf(0.75);
            glClear(GL_DEPTH_BUFFER_BIT);
    
            // Revert to normal framebuffer to test depth shader
            glBindFramebuffer(GL_FRAMEBUFFER, 0);
            glViewport(0, 0, w, h);
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            glClearDepthf(0.0f);
            ASSERT_GL_NO_ERROR();
    
            glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
            ASSERT_GL_NO_ERROR();
    
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, depthTexture);
    
            glUseProgram(mProgram);
            ASSERT_GL_NO_ERROR();
    
            glUniform1i(shadowMapLocation, 0);
    
            const GLfloat gTriangleVertices[] = {-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f};
    
            glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, gTriangleVertices);
            ASSERT_GL_NO_ERROR();
            glEnableVertexAttribArray(positionLocation);
            ASSERT_GL_NO_ERROR();
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
            ASSERT_GL_NO_ERROR();
    
            GLuint pixels[1];
            glReadPixels(w / 2, h / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
            ASSERT_GL_NO_ERROR();
    
            // The GLES 3.x spec says that the depth texture sample can be found in the RED component.
            // However, the OES_depth_texture indicates that the depth value is treated as luminance and
            // is in all the color components. Multiple implementations implement a workaround that
            // follows the OES_depth_texture behavior if the internalformat given at glTexImage2D was a
            // unsized format (e.g. DEPTH_COMPONENT) and the GLES 3.x behavior if it was a sized
            // internalformat such as GL_DEPTH_COMPONENT24. The shader will write out a different color
            // depending on if it sees the texture sample in only the RED component.
            if (useSizedComponent)
            {
                ASSERT_NE(pixels[0], 0xff0000ff);
            }
            else
            {
                ASSERT_EQ(pixels[0], 0xff0000ff);
            }
    
            glBindFramebuffer(GL_FRAMEBUFFER, 0);
            glDeleteProgram(mProgram);
        }
    };
    
    // Test depth texture compatibility with OES_depth_texture. Uses unsized internformat.
    TEST_P(Texture2DDepthTest, DepthTextureES2Compatibility)
    {
        ANGLE_SKIP_TEST_IF(IsD3D11());
        ANGLE_SKIP_TEST_IF(IsIntel() && IsD3D9());
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_depth_texture") &&
                           !IsGLExtensionEnabled("GL_OES_depth_texture"));
        // http://anglebug.com/4092
        ANGLE_SKIP_TEST_IF(IsOpenGL() || IsOpenGLES());
        ANGLE_SKIP_TEST_IF(IsARM64() && IsWindows() && IsD3D());
    
        // http://anglebug.com/4908
        ANGLE_SKIP_TEST_IF(IsIntel() && IsMetal());
    
        // When the depth texture is specified with unsized internalformat implementations follow
        // OES_depth_texture behavior. Otherwise they follow GLES 3.0 behavior.
        testBehavior(false);
    }
    
    // Test depth texture compatibility with GLES3 using sized internalformat.
    TEST_P(Texture2DDepthTest, DepthTextureES3Compatibility)
    {
        ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
    
        // http://anglebug.com/5243
        ANGLE_SKIP_TEST_IF(IsMetal() && !IsMetalTextureSwizzleAvailable());
    
        testBehavior(true);
    }
    
    // Tests unpacking into the unsized GL_ALPHA format.
    TEST_P(Texture2DTestES3, UnsizedAlphaUnpackBuffer)
    {
        // Initialize the texure.
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, getWindowWidth(), getWindowHeight(), 0, GL_ALPHA,
                     GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        std::vector<GLubyte> bufferData(getWindowWidth() * getWindowHeight(), 127);
    
        // Pull in the color data from the unpack buffer.
        GLBuffer unpackBuffer;
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, unpackBuffer.get());
        glBufferData(GL_PIXEL_UNPACK_BUFFER, getWindowWidth() * getWindowHeight(), bufferData.data(),
                     GL_STATIC_DRAW);
    
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, getWindowWidth(), getWindowHeight(), GL_ALPHA,
                        GL_UNSIGNED_BYTE, nullptr);
    
        // Clear to a weird color to make sure we're drawing something.
        glClearColor(0.5f, 0.8f, 1.0f, 0.2f);
        glClear(GL_COLOR_BUFFER_BIT);
    
        // Draw with the alpha texture and verify.
        drawQuad(mProgram, "position", 0.5f);
    
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_NEAR(0, 0, 0, 0, 0, 127, 1);
    }
    
    // Ensure stale unpack data doesn't propagate in D3D11.
    TEST_P(Texture2DTestES3, StaleUnpackData)
    {
        // Init unpack buffer.
        GLsizei pixelCount = getWindowWidth() * getWindowHeight() / 2;
        std::vector<GLColor> pixels(pixelCount, GLColor::red);
    
        GLBuffer unpackBuffer;
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, unpackBuffer.get());
        GLsizei bufferSize = pixelCount * sizeof(GLColor);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, bufferSize, pixels.data(), GL_STATIC_DRAW);
    
        // Create from unpack buffer.
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, getWindowWidth() / 2, getWindowHeight() / 2, 0,
                     GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        drawQuad(mProgram, "position", 0.5f);
    
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    
        // Fill unpack with green, recreating buffer.
        pixels.assign(getWindowWidth() * getWindowHeight(), GLColor::green);
        GLsizei size2 = getWindowWidth() * getWindowHeight() * sizeof(GLColor);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, size2, pixels.data(), GL_STATIC_DRAW);
    
        // Reinit texture with green.
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, getWindowWidth() / 2, getWindowHeight() / 2, GL_RGBA,
                        GL_UNSIGNED_BYTE, nullptr);
    
        drawQuad(mProgram, "position", 0.5f);
    
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    }
    
    // Ensure that texture parameters passed as floats that are converted to ints are rounded before
    // validating they are less than 0.
    TEST_P(Texture2DTestES3, TextureBaseMaxLevelRoundingValidation)
    {
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
    
        // Use a negative number that will round to zero when converted to an integer
        // According to the spec(2.3.1 Data Conversion For State - Setting Commands):
        // "Validation of values performed by state-setting commands is performed after conversion,
        // unless specified otherwise for a specific command."
        GLfloat param = -7.30157126e-07f;
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, param);
        EXPECT_GL_NO_ERROR();
    
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, param);
        EXPECT_GL_NO_ERROR();
    }
    
    // This test covers a D3D format redefinition bug for 3D textures. The base level format was not
    // being properly checked, and the texture storage of the previous texture format was persisting.
    // This would result in an ASSERT in debug and incorrect rendering in release.
    // See http://anglebug.com/1609 and WebGL 2 test conformance2/misc/views-with-offsets.html.
    TEST_P(Texture3DTestES3, FormatRedefinitionBug)
    {
        GLTexture tex;
        glBindTexture(GL_TEXTURE_3D, tex.get());
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.get());
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex.get(), 0, 0);
    
        glCheckFramebufferStatus(GL_FRAMEBUFFER);
    
        std::vector<uint8_t> pixelData(100, 0);
    
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB565, 1, 1, 1, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, nullptr);
        glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 1, 1, 1, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
                        pixelData.data());
    
        ASSERT_GL_NO_ERROR();
    }
    
    // Test glTexSubImage using PBO to 3D texture that expose the regression bug
    // https://issuetracker.google.com/170657065
    TEST_P(Texture3DTestES3, TexSubImageWithPBO)
    {
        GLTexture tex;
    
        GLuint pbo;
        glGenBuffers(1, &pbo);
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
        std::vector<uint8_t> pixelData(128 * 128 * 8 * 4, 0x1f);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, 128 * 128 * 8 * 4, pixelData.data(), GL_STATIC_DRAW);
    
        glBindTexture(GL_TEXTURE_3D, tex.get());
        glTexStorage3D(GL_TEXTURE_3D, 8, GL_RGBA8, 128, 128, 8);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 128, 128, 8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexSubImage3D(GL_TEXTURE_3D, 1, 0, 0, 0, 64, 64, 4, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexSubImage3D(GL_TEXTURE_3D, 2, 0, 0, 0, 32, 32, 2, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexSubImage3D(GL_TEXTURE_3D, 3, 0, 0, 0, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexSubImage3D(GL_TEXTURE_3D, 4, 0, 0, 0, 8, 8, 1, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexSubImage3D(GL_TEXTURE_3D, 5, 0, 0, 0, 4, 4, 1, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexSubImage3D(GL_TEXTURE_3D, 6, 0, 0, 0, 2, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexSubImage3D(GL_TEXTURE_3D, 7, 0, 0, 0, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
    
        glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 128, 128, 8, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexSubImage3D(GL_TEXTURE_3D, 1, 0, 0, 0, 64, 64, 4, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glTexSubImage3D(GL_TEXTURE_3D, 2, 0, 0, 0, 32, 32, 2, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        ASSERT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
    }
    
    // Test basic pixel unpack buffer OOB checks when uploading to a 2D or 3D texture
    TEST_P(Texture3DTestES3, BasicUnpackBufferOOB)
    {
        // 2D tests
        {
            GLTexture tex;
            glBindTexture(GL_TEXTURE_2D, tex.get());
    
            GLBuffer pbo;
            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.get());
    
            // Test OOB
            glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(GLColor) * 2 * 2 - 1, nullptr, GL_STATIC_DRAW);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
            ASSERT_GL_ERROR(GL_INVALID_OPERATION);
    
            // Test OOB
            glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(GLColor) * 2 * 2, nullptr, GL_STATIC_DRAW);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
            ASSERT_GL_NO_ERROR();
        }
    
        // 3D tests
        {
            GLTexture tex;
            glBindTexture(GL_TEXTURE_3D, tex.get());
    
            GLBuffer pbo;
            glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.get());
    
            // Test OOB
            glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(GLColor) * 2 * 2 * 2 - 1, nullptr,
                         GL_STATIC_DRAW);
            glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
            ASSERT_GL_ERROR(GL_INVALID_OPERATION);
    
            // Test OOB
            glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(GLColor) * 2 * 2 * 2, nullptr, GL_STATIC_DRAW);
            glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
            ASSERT_GL_NO_ERROR();
        }
    }
    
    // Tests behaviour with a single texture and multiple sampler objects.
    TEST_P(Texture2DTestES3, SingleTextureMultipleSamplers)
    {
        GLint maxTextureUnits = 0;
        glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
        ANGLE_SKIP_TEST_IF(maxTextureUnits < 4);
    
        constexpr int kSize = 16;
    
        // Make a single-level texture, fill it with red.
        std::vector<GLColor> redColors(kSize * kSize, GLColor::red);
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     redColors.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // Simple confidence check.
        draw2DTexturedQuad(0.5f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    
        // Bind texture to unit 1 with a sampler object making it incomplete.
        GLSampler sampler;
        glBindSampler(0, sampler);
        glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // Make a mipmap texture, fill it with blue.
        std::vector<GLColor> blueColors(kSize * kSize, GLColor::blue);
        GLTexture mipmapTex;
        glBindTexture(GL_TEXTURE_2D, mipmapTex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     blueColors.data());
        glGenerateMipmap(GL_TEXTURE_2D);
    
        // Draw with the sampler, expect blue.
        draw2DTexturedQuad(0.5f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    
        // Simple multitexturing program.
        constexpr char kVS[] =
            "#version 300 es\n"
            "in vec2 position;\n"
            "out vec2 texCoord;\n"
            "void main()\n"
            "{\n"
            "    gl_Position = vec4(position, 0, 1);\n"
            "    texCoord = position * 0.5 + vec2(0.5);\n"
            "}";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "precision mediump float;\n"
            "in vec2 texCoord;\n"
            "uniform sampler2D tex1;\n"
            "uniform sampler2D tex2;\n"
            "uniform sampler2D tex3;\n"
            "uniform sampler2D tex4;\n"
            "out vec4 color;\n"
            "void main()\n"
            "{\n"
            "    color = (texture(tex1, texCoord) + texture(tex2, texCoord) \n"
            "          +  texture(tex3, texCoord) + texture(tex4, texCoord)) * 0.25;\n"
            "}";
    
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        std::array<GLint, 4> texLocations = {
            {glGetUniformLocation(program, "tex1"), glGetUniformLocation(program, "tex2"),
             glGetUniformLocation(program, "tex3"), glGetUniformLocation(program, "tex4")}};
        for (GLint location : texLocations)
        {
            ASSERT_NE(-1, location);
        }
    
        // Init the uniform data.
        glUseProgram(program);
        for (GLint location = 0; location < 4; ++location)
        {
            glUniform1i(texLocations[location], location);
        }
    
        // Initialize four samplers
        GLSampler samplers[4];
    
        // 0: non-mipped.
        glBindSampler(0, samplers[0]);
        glSamplerParameteri(samplers[0], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glSamplerParameteri(samplers[0], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // 1: mipped.
        glBindSampler(1, samplers[1]);
        glSamplerParameteri(samplers[1], GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glSamplerParameteri(samplers[1], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // 2: non-mipped.
        glBindSampler(2, samplers[2]);
        glSamplerParameteri(samplers[2], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glSamplerParameteri(samplers[2], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // 3: mipped.
        glBindSampler(3, samplers[3]);
        glSamplerParameteri(samplers[3], GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glSamplerParameteri(samplers[3], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // Bind two blue mipped textures and two single layer textures, should all draw.
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex);
    
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, mipmapTex);
    
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, tex);
    
        glActiveTexture(GL_TEXTURE3);
        glBindTexture(GL_TEXTURE_2D, mipmapTex);
    
        ASSERT_GL_NO_ERROR();
    
        drawQuad(program, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_NEAR(0, 0, 128, 0, 128, 255, 2);
    
        // Bind four single layer textures, two should be incomplete.
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, tex);
    
        glActiveTexture(GL_TEXTURE3);
        glBindTexture(GL_TEXTURE_2D, tex);
    
        drawQuad(program, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_NEAR(0, 0, 128, 0, 0, 255, 2);
    }
    
    // The test is added to cover http://anglebug.com/2153. Cubemap completeness checks used to start
    // always at level 0 instead of the base level resulting in an incomplete texture if the faces at
    // level 0 are not created. The test creates a cubemap texture, specifies the images only for mip
    // level 1 filled with white color, updates the base level to be 1 and renders a quad. The program
    // samples the cubemap using a direction vector (1,1,1).
    TEST_P(TextureCubeTestES3, SpecifyAndSampleFromBaseLevel1)
    {
        // Check http://anglebug.com/2155.
        ANGLE_SKIP_TEST_IF(IsOSX() && IsNVIDIA());
    
        constexpr char kVS[] =
            R"(#version 300 es
            precision mediump float;
            in vec3 pos;
            void main() {
                gl_Position = vec4(pos, 1.0);
            })";
    
        constexpr char kFS[] =
            R"(#version 300 es
            precision mediump float;
            out vec4 color;
            uniform samplerCube uTex;
            void main(){
                color = texture(uTex, vec3(1.0));
            })";
    
        ANGLE_GL_PROGRAM(program, kVS, kFS);
        glUseProgram(program);
    
        glUniform1i(glGetUniformLocation(program, "uTex"), 0);
        glActiveTexture(GL_TEXTURE0);
    
        GLTexture cubeTex;
        glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTex);
    
        const int kFaceWidth  = 1;
        const int kFaceHeight = 1;
        std::vector<uint32_t> texData(kFaceWidth * kFaceHeight, 0xFFFFFFFF);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 1, GL_RGBA8, kFaceWidth, kFaceHeight, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, texData.data());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 1, GL_RGBA8, kFaceWidth, kFaceHeight, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, texData.data());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 1, GL_RGBA8, kFaceWidth, kFaceHeight, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, texData.data());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 1, GL_RGBA8, kFaceWidth, kFaceHeight, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, texData.data());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 1, GL_RGBA8, kFaceWidth, kFaceHeight, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, texData.data());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 1, GL_RGBA8, kFaceWidth, kFaceHeight, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, texData.data());
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 1);
    
        drawQuad(program, "pos", 0.5f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, angle::GLColor::white);
    }
    
    // Verify that using negative texture base level and max level generates GL_INVALID_VALUE.
    TEST_P(Texture2DTestES3, NegativeTextureBaseLevelAndMaxLevel)
    {
        GLuint texture = create2DTexture();
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, -1);
        EXPECT_GL_ERROR(GL_INVALID_VALUE);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, -1);
        EXPECT_GL_ERROR(GL_INVALID_VALUE);
    
        glDeleteTextures(1, &texture);
        EXPECT_GL_NO_ERROR();
    }
    
    // Test setting base level after calling generateMipmap on a LUMA texture.
    // Covers http://anglebug.com/2498
    TEST_P(Texture2DTestES3, GenerateMipmapAndBaseLevelLUMA)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        constexpr const GLsizei kWidth  = 8;
        constexpr const GLsizei kHeight = 8;
        std::array<GLubyte, kWidth * kHeight * 2> whiteData;
        whiteData.fill(255u);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, kWidth, kHeight, 0, GL_LUMINANCE_ALPHA,
                     GL_UNSIGNED_BYTE, whiteData.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glGenerateMipmap(GL_TEXTURE_2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, angle::GLColor::white);
    }
    
    // Incompatible levels with non-mipmap filtering should work.
    TEST_P(Texture2DTestES3, IncompatibleMipsButNoMipmapFiltering)
    {
        // http://anglebug.com/4780
        ANGLE_SKIP_TEST_IF(IsVulkan());
    
        // http://anglebug.com/4782
        ANGLE_SKIP_TEST_IF(IsOpenGL() && IsWindows() && (IsAMD() || IsIntel()));
    
        // http://anglebug.com/4786
        ANGLE_SKIP_TEST_IF(IsOpenGLES() && IsNVIDIAShield());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        constexpr const GLsizei kSize = 8;
        const std::vector<GLColor> kLevel0Data(kSize * kSize, GLColor::blue);
        const std::vector<GLColor> kLevel1Data(kSize * kSize, GLColor::red);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kLevel0Data.data());
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kLevel1Data.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        EXPECT_GL_NO_ERROR();
    
        // Draw with base level 0.  The GL_LINEAR filtering ensures the texture's image is not created
        // with mipmap.
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, kLevel0Data[0]);
    
        // Verify draw with level 1.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, kLevel1Data[0]);
    
        // Verify draw with level 0 again
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, kLevel0Data[0]);
    }
    
    // Enabling mipmap filtering after previously having used the texture without it should work.
    TEST_P(Texture2DTestES3, NoMipmapDrawThenMipmapDraw)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        constexpr const GLsizei kSize = 8;
        const std::vector<GLColor> kLevel0Data(kSize * kSize, GLColor::blue);
        const std::vector<GLColor> kLevelOtherData(kSize * kSize, GLColor::red);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kLevel0Data.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        EXPECT_GL_NO_ERROR();
    
        // Draw so the texture's image is allocated.
        drawQuad(mProgram, "position", 0.5f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, kLevel0Data[0]);
    
        // Specify the rest of the image
        for (GLint mip = 1; (kSize >> mip) >= 1; ++mip)
        {
            glTexImage2D(GL_TEXTURE_2D, mip, GL_RGBA, kSize >> mip, kSize >> mip, 0, GL_RGBA,
                         GL_UNSIGNED_BYTE, kLevelOtherData.data());
        }
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
    
        ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Texture2DLod(), essl3_shaders::fs::Texture2DLod());
        glUseProgram(program);
        GLint textureLoc = glGetUniformLocation(program, essl3_shaders::Texture2DUniform());
        GLint lodLoc     = glGetUniformLocation(program, essl3_shaders::LodUniform());
        ASSERT_NE(-1, textureLoc);
        ASSERT_NE(-1, lodLoc);
        glUniform1i(textureLoc, 0);
    
        // Verify the mips
        for (GLint mip = 0; (kSize >> mip) >= 1; ++mip)
        {
            glUniform1f(lodLoc, mip);
            drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_GL_NO_ERROR();
            EXPECT_PIXEL_COLOR_EQ(0, 0, (mip == 0 ? kLevel0Data[0] : kLevelOtherData[0]));
        }
    }
    
    // Disabling mipmap filtering after previously having used the texture with it should work.
    TEST_P(Texture2DTestES3, MipmapDrawThenNoMipmapDraw)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        constexpr const GLsizei kSize = 8;
        const std::vector<GLColor> kLevel0Data(kSize * kSize, GLColor::blue);
        const std::vector<GLColor> kLevelOtherData(kSize * kSize, GLColor::red);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kLevel0Data.data());
        for (GLint mip = 1; (kSize >> mip) >= 1; ++mip)
        {
            glTexImage2D(GL_TEXTURE_2D, mip, GL_RGBA, kSize >> mip, kSize >> mip, 0, GL_RGBA,
                         GL_UNSIGNED_BYTE, kLevelOtherData.data());
        }
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        EXPECT_GL_NO_ERROR();
    
        ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Texture2DLod(), essl3_shaders::fs::Texture2DLod());
        glUseProgram(program);
        GLint textureLoc = glGetUniformLocation(program, essl3_shaders::Texture2DUniform());
        GLint lodLoc     = glGetUniformLocation(program, essl3_shaders::LodUniform());
        ASSERT_NE(-1, textureLoc);
        ASSERT_NE(-1, lodLoc);
        glUniform1i(textureLoc, 0);
    
        // Verify the mips.
        for (GLint mip = 0; (kSize >> mip) >= 1; ++mip)
        {
            glUniform1f(lodLoc, mip);
            drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_GL_NO_ERROR();
            EXPECT_PIXEL_COLOR_EQ(0, 0, (mip == 0 ? kLevel0Data[0] : kLevelOtherData[0]));
        }
    
        // Disable mipmapping and verify mips again.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        for (GLint mip = 0; (kSize >> mip) >= 1; ++mip)
        {
            glUniform1f(lodLoc, mip);
            drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_GL_NO_ERROR();
            EXPECT_PIXEL_COLOR_EQ(0, 0, kLevel0Data[0]);
        }
    }
    
    // Respecify texture with more mips.
    TEST_P(Texture2DTestES3, RespecifyWithMoreMips)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        constexpr const GLsizei kSize = 8;
        const std::vector<GLColor> kLevelEvenData(kSize * kSize, GLColor::blue);
        const std::vector<GLColor> kLevelOddData(kSize * kSize * 4, GLColor::red);
    
        auto getLevelData = [&](GLint mip) {
            return mip % 2 == 0 ? kLevelEvenData.data() : kLevelOddData.data();
        };
    
        for (GLint mip = 0; (kSize >> mip) >= 1; ++mip)
        {
            glTexImage2D(GL_TEXTURE_2D, mip, GL_RGBA, kSize >> mip, kSize >> mip, 0, GL_RGBA,
                         GL_UNSIGNED_BYTE, getLevelData(mip));
        }
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        EXPECT_GL_NO_ERROR();
    
        ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Texture2DLod(), essl3_shaders::fs::Texture2DLod());
        glUseProgram(program);
        GLint textureLoc = glGetUniformLocation(program, essl3_shaders::Texture2DUniform());
        GLint lodLoc     = glGetUniformLocation(program, essl3_shaders::LodUniform());
        ASSERT_NE(-1, textureLoc);
        ASSERT_NE(-1, lodLoc);
        glUniform1i(textureLoc, 0);
    
        // Verify the mips.
        for (GLint mip = 0; (kSize >> mip) >= 1; ++mip)
        {
            glUniform1f(lodLoc, mip);
            drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_GL_NO_ERROR();
            EXPECT_PIXEL_COLOR_EQ(0, 0, getLevelData(mip)[0]);
        }
    
        // Respecify the texture with more mips, without changing any parameters.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize * 2, kSize * 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kLevelOddData.data());
        for (GLint mip = 0; (kSize >> mip) >= 1; ++mip)
        {
            glTexImage2D(GL_TEXTURE_2D, mip + 1, GL_RGBA, kSize >> mip, kSize >> mip, 0, GL_RGBA,
                         GL_UNSIGNED_BYTE, getLevelData(mip));
        }
    
        // Verify the mips.
        for (GLint mip = 0; ((kSize * 2) >> mip) >= 1; ++mip)
        {
            glUniform1f(lodLoc, mip);
            drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
            EXPECT_GL_NO_ERROR();
            EXPECT_PIXEL_COLOR_EQ(0, 0, getLevelData(mip - 1)[0]);
        }
    }
    
    // Covers a bug in the D3D11 backend: http://anglebug.com/2772
    // When using a sampler the texture was created as if it has mipmaps,
    // regardless what you specified in GL_TEXTURE_MIN_FILTER via
    // glSamplerParameteri() -- mistakenly the default value
    // GL_NEAREST_MIPMAP_LINEAR or the value set via glTexParameteri() was
    // evaluated.
    // If you didn't provide mipmaps and didn't let the driver generate them
    // this led to not sampling your texture data when minification occurred.
    TEST_P(Texture2DTestES3, MinificationWithSamplerNoMipmapping)
    {
        constexpr char kVS[] =
            "#version 300 es\n"
            "out vec2 texcoord;\n"
            "in vec4 position;\n"
            "void main()\n"
            "{\n"
            "    gl_Position = vec4(position.xy * 0.1, 0.0, 1.0);\n"
            "    texcoord = (position.xy * 0.5) + 0.5;\n"
            "}\n";
    
        constexpr char kFS[] =
            "#version 300 es\n"
            "precision highp float;\n"
            "uniform highp sampler2D tex;\n"
            "in vec2 texcoord;\n"
            "out vec4 fragColor;\n"
            "void main()\n"
            "{\n"
            "    fragColor = texture(tex, texcoord);\n"
            "}\n";
    
        ANGLE_GL_PROGRAM(program, kVS, kFS);
    
        GLSampler sampler;
        glBindSampler(0, sampler);
        glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        const GLsizei texWidth  = getWindowWidth();
        const GLsizei texHeight = getWindowHeight();
        const std::vector<GLColor> whiteData(texWidth * texHeight, GLColor::white);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     whiteData.data());
        EXPECT_GL_NO_ERROR();
    
        drawQuad(program, "position", 0.5f);
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, angle::GLColor::white);
    }
    
    // Test that clears due to emulated formats are to the correct level given non-zero base level.
    TEST_P(Texture2DTestES3, NonZeroBaseEmulatedClear)
    {
        // Tests behavior of the Vulkan backend with emulated formats.
        ANGLE_SKIP_TEST_IF(!IsVulkan());
    
        setUpProgram();
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
        glTexImage2D(GL_TEXTURE_2D, 1, GL_RGB, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
        glTexImage2D(GL_TEXTURE_2D, 2, GL_RGB, 4, 4, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
        glTexImage2D(GL_TEXTURE_2D, 3, GL_RGB, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
        glTexImage2D(GL_TEXTURE_2D, 4, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 4);
        EXPECT_GL_NO_ERROR();
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
    }
    
    // Draw a quad with an integer texture with a non-zero base level, and test that the color of the
    // texture is output.
    TEST_P(Texture2DIntegerTestES3, IntegerTextureNonZeroBaseLevel)
    {
        // http://anglebug.com/3478
        ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsDesktopOpenGL());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        int width     = getWindowWidth();
        int height    = getWindowHeight();
        GLColor color = GLColor::green;
        std::vector<GLColor> pixels(width * height, color);
        GLint baseLevel = 1;
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, baseLevel);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, baseLevel, GL_RGBA8UI, width, height, 0, GL_RGBA_INTEGER,
                     GL_UNSIGNED_BYTE, pixels.data());
    
        setUpProgram();
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, color);
        EXPECT_PIXEL_COLOR_EQ(width - 1, height - 1, color);
    }
    
    // Draw a quad with an integer cube texture with a non-zero base level, and test that the color of
    // the texture is output.
    TEST_P(TextureCubeIntegerTestES3, IntegerCubeTextureNonZeroBaseLevel)
    {
        // All output checks returned black, rather than the texture color.
        ANGLE_SKIP_TEST_IF(IsOSX() && IsOpenGL());
    
        glActiveTexture(GL_TEXTURE0);
    
        glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
        GLint baseLevel = 1;
        int width       = getWindowWidth();
        int height      = getWindowHeight();
        GLColor color   = GLColor::green;
        std::vector<GLColor> pixels(width * height, color);
        for (GLenum faceIndex = 0; faceIndex < 6; faceIndex++)
        {
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, baseLevel, GL_RGBA8UI, width,
                         height, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, pixels.data());
            EXPECT_GL_NO_ERROR();
        }
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, baseLevel);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        glUseProgram(mProgram);
        glUniform1i(mTextureCubeUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, color);
        EXPECT_PIXEL_COLOR_EQ(width - 1, 0, color);
        EXPECT_PIXEL_COLOR_EQ(0, height - 1, color);
        EXPECT_PIXEL_COLOR_EQ(width - 1, height - 1, color);
    }
    
    // This test sets up a cube map with four distincly colored MIP levels.
    // The size of the texture and the geometry is chosen such that levels 1 or 2 should be chosen at
    // the corners of the screen.
    TEST_P(TextureCubeIntegerEdgeTestES3, IntegerCubeTextureCorner)
    {
        glActiveTexture(GL_TEXTURE0);
    
        glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
        int width  = getWindowWidth();
        int height = getWindowHeight();
        ASSERT_EQ(width, height);
        GLColor color[4] = {GLColor::white, GLColor::green, GLColor::blue, GLColor::red};
        for (GLint level = 0; level < 4; level++)
        {
            for (GLenum faceIndex = 0; faceIndex < 6; faceIndex++)
            {
                int levelWidth  = (2 * width) >> level;
                int levelHeight = (2 * height) >> level;
                std::vector<GLColor> pixels(levelWidth * levelHeight, color[level]);
                glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, level, GL_RGBA8UI, levelWidth,
                             levelHeight, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, pixels.data());
                EXPECT_GL_NO_ERROR();
            }
        }
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, 3);
    
        glUseProgram(mProgram);
        glUniform1i(mTextureCubeUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
    
        ASSERT_GL_NO_ERROR();
        // Check that we do not read from levels 0 or 3. Levels 1 and 2 are both acceptable.
        EXPECT_EQ(ReadColor(0, 0).R, 0);
        EXPECT_EQ(ReadColor(width - 1, 0).R, 0);
        EXPECT_EQ(ReadColor(0, height - 1).R, 0);
        EXPECT_EQ(ReadColor(width - 1, height - 1).R, 0);
    }
    
    // Draw a quad with an integer texture with a non-zero base level, and test that the color of the
    // texture is output.
    TEST_P(Texture2DIntegerProjectiveOffsetTestES3, NonZeroBaseLevel)
    {
        // Fails on AMD: http://crbug.com/967796
        ANGLE_SKIP_TEST_IF(IsAMD() && IsWindows() && IsOpenGL());
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        int width     = getWindowWidth();
        int height    = getWindowHeight();
        GLColor color = GLColor::green;
        std::vector<GLColor> pixels(width * height, color);
        GLint baseLevel = 1;
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, baseLevel);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexImage2D(GL_TEXTURE_2D, baseLevel, GL_RGBA8UI, width, height, 0, GL_RGBA_INTEGER,
                     GL_UNSIGNED_BYTE, pixels.data());
    
        setUpProgram();
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, color);
        EXPECT_PIXEL_COLOR_EQ(width - 1, height - 1, color);
    }
    
    // Draw a quad with an integer texture with a non-zero base level, and test that the color of the
    // texture is output.
    TEST_P(Texture2DArrayIntegerTestES3, NonZeroBaseLevel)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
        int width     = getWindowWidth();
        int height    = getWindowHeight();
        int depth     = 2;
        GLColor color = GLColor::green;
        std::vector<GLColor> pixels(width * height * depth, color);
        GLint baseLevel = 1;
        glTexImage3D(GL_TEXTURE_2D_ARRAY, baseLevel, GL_RGBA8UI, width, height, depth, 0,
                     GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, pixels.data());
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, baseLevel);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, color);
        EXPECT_PIXEL_COLOR_EQ(width - 1, height - 1, color);
    }
    
    // Draw a quad with an integer 3D texture with a non-zero base level, and test that the color of the
    // texture is output.
    TEST_P(Texture3DIntegerTestES3, NonZeroBaseLevel)
    {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, mTexture3D);
        int width     = getWindowWidth();
        int height    = getWindowHeight();
        int depth     = 2;
        GLColor color = GLColor::green;
        std::vector<GLColor> pixels(width * height * depth, color);
        GLint baseLevel = 1;
        glTexImage3D(GL_TEXTURE_3D, baseLevel, GL_RGBA8UI, width, height, depth, 0, GL_RGBA_INTEGER,
                     GL_UNSIGNED_BYTE, pixels.data());
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, baseLevel);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        drawQuad(mProgram, "position", 0.5f);
    
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, color);
        EXPECT_PIXEL_COLOR_EQ(width - 1, height - 1, color);
    }
    
    // Test that uses glCompressedTexSubImage2D combined with a PBO
    TEST_P(PBOCompressedTextureTest, PBOCompressedSubImage)
    {
        // ETC texture formats are not supported on Mac OpenGL. http://anglebug.com/3853
        ANGLE_SKIP_TEST_IF(IsOSX() && IsDesktopOpenGL());
        // http://anglebug.com/4115
        ANGLE_SKIP_TEST_IF(IsAMD() && IsWindows() && IsDesktopOpenGL());
        ANGLE_SKIP_TEST_IF(IsIntel() && IsWindows() && IsDesktopOpenGL());
    
        if (getClientMajorVersion() < 3)
        {
            ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
            ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
            ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_compressed_ETC2_RGB8_texture"));
        }
    
        const GLuint width  = 4u;
        const GLuint height = 4u;
    
        setWindowWidth(width);
        setWindowHeight(height);
    
        // Setup primary Texture
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
        if (getClientMajorVersion() < 3)
        {
            glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGB8_ETC2, width, height);
        }
        else
        {
            glTexStorage2D(GL_TEXTURE_2D, 1, GL_COMPRESSED_RGB8_ETC2, width, height);
        }
        ASSERT_GL_NO_ERROR();
    
        // Setup PBO and fill it with a red
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mPBO);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, width * height / 2u, kCompressedImageETC2, GL_STATIC_DRAW);
        ASSERT_GL_NO_ERROR();
    
        // Write PBO to mTexture
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_COMPRESSED_RGB8_ETC2,
                                  width * height / 2u, nullptr);
        ASSERT_GL_NO_ERROR();
    
        setUpProgram();
        // Draw using PBO updated texture
        glUseProgram(mProgram);
        glUniform1i(mTexture2DUniformLocation, 0);
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        ASSERT_GL_NO_ERROR();
    }
    
    // Test using ETC1_RGB8 with subimage updates
    TEST_P(ETC1CompressedTextureTest, ETC1CompressedSubImage)
    {
        // ETC texture formats are not supported on Mac OpenGL. http://anglebug.com/3853
        ANGLE_SKIP_TEST_IF(IsOSX() && IsDesktopOpenGL());
    
        ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 &&
                           !IsGLExtensionEnabled("GL_EXT_texture_storage"));
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_compressed_ETC1_RGB8_sub_texture"));
    
        const GLuint width  = 4u;
        const GLuint height = 4u;
    
        setWindowWidth(width);
        setWindowHeight(height);
    
        // Setup primary Texture
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
        if (getClientMajorVersion() < 3)
        {
            glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_ETC1_RGB8_OES, width, height);
        }
        else
        {
            glTexStorage2D(GL_TEXTURE_2D, 1, GL_ETC1_RGB8_OES, width, height);
        }
        ASSERT_GL_NO_ERROR();
    
        // Populate a subimage of the texture
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_ETC1_RGB8_OES,
                                  width * height / 2u, kCompressedImageETC2);
        ASSERT_GL_NO_ERROR();
    
        // Render and ensure we get red
        glUseProgram(mProgram);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
    
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        ASSERT_GL_NO_ERROR();
    }
    
    // Fully-define a NPOT compressed texture and draw; set MAX_LEVEL and draw; then increase
    // MAX_LEVEL and draw.  This used to cause Vulkan validation errors.
    TEST_P(ETC1CompressedTextureTest, ETC1CompressedImageNPOT)
    {
        // ETC texture formats are not supported on Mac OpenGL. http://anglebug.com/3853
        ANGLE_SKIP_TEST_IF(IsOSX() && IsDesktopOpenGL());
    
        ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_compressed_ETC1_RGB8_sub_texture"));
    
        const GLuint width  = 5u;
        const GLuint height = 5u;
        // round up to the nearest block size
        const GLsizei imageSize = 8 * 8 / 2;
        // smallest block size
        const GLsizei minImageSize = 4 * 4 / 2;
    
        uint8_t data[imageSize] = {0};
    
        setWindowWidth(width);
        setWindowHeight(height);
    
        // Setup primary Texture
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_ETC1_RGB8_OES, width, height, 0, imageSize, data);
        ASSERT_GL_NO_ERROR();
    
        glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_ETC1_RGB8_OES, width / 2, height / 2, 0,
                               minImageSize, data);
        ASSERT_GL_NO_ERROR();
    
        glCompressedTexImage2D(GL_TEXTURE_2D, 2, GL_ETC1_RGB8_OES, width / 4, height / 4, 0,
                               minImageSize, data);
        ASSERT_GL_NO_ERROR();
    
        glUseProgram(mProgram);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
    }
    
    // Define two NPOT compressed textures, set MAX_LEVEL, draw, and swap buffers
    // with the two textures. This used to cause release of staging buffers
    // that have not been flushed.
    TEST_P(ETC1CompressedTextureTest, ETC1CompressedImageDraws)
    {
        // ETC texture formats are not supported on Mac OpenGL. http://anglebug.com/3853
        ANGLE_SKIP_TEST_IF(IsOSX() && IsDesktopOpenGL());
    
        ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_compressed_ETC1_RGB8_sub_texture"));
    
        const GLuint width  = 384u;
        const GLuint height = 384u;
        // round up to the nearest block size
        const GLsizei imageSize = width * height / 2;
    
        uint8_t data[imageSize] = {0};
    
        setWindowWidth(width);
        setWindowHeight(height);
    
        const GLuint smallerWidth  = 384u;
        const GLuint smallerHeight = 320u;
        // round up to the nearest block size
        const GLsizei smallerImageSize = smallerWidth * smallerHeight / 2;
    
        // Setup primary Texture
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_ETC1_RGB8_OES, smallerWidth, smallerHeight, 0,
                               smallerImageSize, data);
        ASSERT_GL_NO_ERROR();
    
        glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_ETC1_RGB8_OES, 192, 160, 0, 15360, data);
        ASSERT_GL_NO_ERROR();
    
        GLTexture largerTexture;
        glBindTexture(GL_TEXTURE_2D, largerTexture);
    
        glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_ETC1_RGB8_OES, width, height, 0, imageSize, data);
        ASSERT_GL_NO_ERROR();
    
        glCompressedTexImage2D(GL_TEXTURE_2D, 1, GL_ETC1_RGB8_OES, 192, 192, 0, 18432, data);
        ASSERT_GL_NO_ERROR();
    
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        glUseProgram(mProgram);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        swapBuffers();
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        swapBuffers();
    
        glBindTexture(GL_TEXTURE_2D, largerTexture);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        swapBuffers();
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        swapBuffers();
    
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
        drawQuad(mProgram, "position", 0.5f);
        swapBuffers();
        ASSERT_GL_NO_ERROR();
    }
    
    // Fully-define a compressed texture and draw; then decrease MAX_LEVEL and draw; then increase
    // MAX_LEVEL and draw.  This used to cause Vulkan validation errors.
    TEST_P(ETC1CompressedTextureTest, ETC1ShrinkThenGrowMaxLevels)
    {
        // ETC texture formats are not supported on Mac OpenGL. http://anglebug.com/3853
        ANGLE_SKIP_TEST_IF(IsOSX() && IsDesktopOpenGL());
    
        ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_compressed_ETC1_RGB8_sub_texture"));
    
        const GLuint width  = 4u;
        const GLuint height = 4u;
    
        setWindowWidth(width);
        setWindowHeight(height);
    
        // Setup primary Texture
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
        if (getClientMajorVersion() < 3)
        {
            glTexStorage2DEXT(GL_TEXTURE_2D, 3, GL_ETC1_RGB8_OES, width, height);
        }
        else
        {
            glTexStorage2D(GL_TEXTURE_2D, 3, GL_ETC1_RGB8_OES, width, height);
        }
        ASSERT_GL_NO_ERROR();
    
        // Populate a subimage of the texture
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_ETC1_RGB8_OES,
                                  width * height / 2u, kCompressedImageETC2);
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, width / 2, height / 2, GL_ETC1_RGB8_OES,
                                  width * height / 2u, kCompressedImageETC2);
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 2, 0, 0, width / 4, height / 4, GL_ETC1_RGB8_OES,
                                  width * height / 2u, kCompressedImageETC2);
        ASSERT_GL_NO_ERROR();
    
        // Set MAX_LEVEL to 2 (the highest level)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
    
        // Render and ensure we get red
        glUseProgram(mProgram);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        ASSERT_GL_NO_ERROR();
    
        // Decrease MAX_LEVEL to 0, render, and ensure we still get red
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        ASSERT_GL_NO_ERROR();
    
        // Increase MAX_LEVEL back to 2, render, and ensure we still get red
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
        drawQuad(mProgram, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
        ASSERT_GL_NO_ERROR();
    }
    
    class TextureBufferTestES31 : public ANGLETest
    {
      protected:
        TextureBufferTestES31() {}
    };
    
    // Test that mutating a buffer attached to a texture returns correct results in query.
    TEST_P(TextureBufferTestES31, QueryWidthAfterBufferResize)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_buffer"));
    
        constexpr GLint kInitialSize                  = 128;
        constexpr std::array<GLint, 4> kModifiedSizes = {96, 192, 32, 256};
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_BUFFER, texture);
    
        GLBuffer buffer;
        glBindBuffer(GL_TEXTURE_BUFFER, buffer);
        glBufferData(GL_TEXTURE_BUFFER, kInitialSize, nullptr, GL_STATIC_DRAW);
    
        glTexBufferEXT(GL_TEXTURE_BUFFER, GL_RGBA8, buffer);
        ASSERT_GL_NO_ERROR();
    
        GLint queryResult = 0;
        glGetTexLevelParameteriv(GL_TEXTURE_BUFFER, 0, GL_TEXTURE_WIDTH, &queryResult);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(queryResult, kInitialSize / 4);
    
        for (GLint modifiedSize : kModifiedSizes)
        {
            glBufferData(GL_TEXTURE_BUFFER, modifiedSize, nullptr, GL_STATIC_DRAW);
            glGetTexLevelParameteriv(GL_TEXTURE_BUFFER, 0, GL_TEXTURE_WIDTH, &queryResult);
            ASSERT_GL_NO_ERROR();
            EXPECT_EQ(queryResult, modifiedSize / 4);
        }
    }
    
    class CopyImageTestES31 : public ANGLETest
    {
      protected:
        CopyImageTestES31() {}
    };
    
    // Test that copies between RGB formats doesn't affect the emulated alpha channel, if any.
    TEST_P(CopyImageTestES31, PreserveEmulatedAlpha)
    {
        ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_copy_image"));
    
        constexpr GLsizei kSize = 1;
    
        GLTexture src, dst;
    
        // Set up the textures
        glBindTexture(GL_TEXTURE_2D, src);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB8, kSize, kSize);
    
        const GLColor kInitColor(50, 100, 150, 200);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSize, kSize, GL_RGB, GL_UNSIGNED_BYTE, &kInitColor);
    
        glBindTexture(GL_TEXTURE_2D, dst);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB8UI, kSize, kSize);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        // Copy from src to dst
        glCopyImageSubDataEXT(src, GL_TEXTURE_2D, 0, 0, 0, 0, dst, GL_TEXTURE_2D, 0, 0, 0, 0, kSize,
                              kSize, 1);
    
        // Bind dst as image
        glBindImageTexture(0, dst, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8UI);
    
        // Create a buffer for output
        constexpr GLsizei kBufferSize = kSize * kSize * sizeof(uint32_t) * 4;
        GLBuffer buffer;
        glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
        glBufferData(GL_SHADER_STORAGE_BUFFER, kBufferSize, nullptr, GL_STATIC_DRAW);
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, buffer);
    
        constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
    layout(rgba8ui, binding = 0) readonly uniform highp uimage2D imageIn;
     layout(std140, binding = 1) buffer dataOut {
         uvec4 data[];
     };
    void main()
    {
        uvec4 color = imageLoad(imageIn, ivec2(0));
        data[0] = color;
    })";
    
        ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
        glUseProgram(program);
        glDispatchCompute(1, 1, 1);
        EXPECT_GL_NO_ERROR();
    
        glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
    
        const uint32_t *ptr = reinterpret_cast<uint32_t *>(
            glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT));
    
        EXPECT_EQ(ptr[0], kInitColor.R);
        EXPECT_EQ(ptr[1], kInitColor.G);
        EXPECT_EQ(ptr[2], kInitColor.B);
    
        // Expect alpha to be 1, even if the RGB format is emulated with RGBA.
        EXPECT_EQ(ptr[3], 1u);
    
        glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
    }
    
    // Use this to select which configurations (e.g. which renderer, which GLES major version) these
    // tests should be run against.
    #define ES2_EMULATE_COPY_TEX_IMAGE()                          \
        WithEmulateCopyTexImage2DFromRenderbuffers(ES2_OPENGL()), \
            WithEmulateCopyTexImage2DFromRenderbuffers(ES2_OPENGLES())
    #define ES3_EMULATE_COPY_TEX_IMAGE()                          \
        WithEmulateCopyTexImage2DFromRenderbuffers(ES3_OPENGL()), \
            WithEmulateCopyTexImage2DFromRenderbuffers(ES3_OPENGLES())
    ANGLE_INSTANTIATE_TEST(Texture2DTest, ANGLE_ALL_TEST_PLATFORMS_ES2, ES2_EMULATE_COPY_TEX_IMAGE());
    ANGLE_INSTANTIATE_TEST_ES2(TextureCubeTest);
    ANGLE_INSTANTIATE_TEST_ES2(Texture2DTestWithDrawScale);
    ANGLE_INSTANTIATE_TEST_ES2(Sampler2DAsFunctionParameterTest);
    ANGLE_INSTANTIATE_TEST_ES2(SamplerArrayTest);
    ANGLE_INSTANTIATE_TEST_ES2(SamplerArrayAsFunctionParameterTest);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DTestES3);
    ANGLE_INSTANTIATE_TEST_ES3_AND(Texture2DTestES3, WithAllocateNonZeroMemory(ES3_VULKAN()));
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DTestES31PPO);
    ANGLE_INSTANTIATE_TEST_ES31(Texture2DTestES31PPO);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DBaseMaxTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(Texture2DBaseMaxTestES3);
    
    ANGLE_INSTANTIATE_TEST_ES2(Texture3DTestES2);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture3DTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(Texture3DTestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DIntegerAlpha1TestES3);
    ANGLE_INSTANTIATE_TEST_ES3(Texture2DIntegerAlpha1TestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DUnsignedIntegerAlpha1TestES3);
    ANGLE_INSTANTIATE_TEST_ES3(Texture2DUnsignedIntegerAlpha1TestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ShadowSamplerPlusSampler3DTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(ShadowSamplerPlusSampler3DTestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SamplerTypeMixTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(SamplerTypeMixTestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DArrayTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(Texture2DArrayTestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TextureSizeTextureArrayTest);
    ANGLE_INSTANTIATE_TEST_ES3(TextureSizeTextureArrayTest);
    
    ANGLE_INSTANTIATE_TEST_ES2(SamplerInStructTest);
    ANGLE_INSTANTIATE_TEST_ES2(SamplerInStructAsFunctionParameterTest);
    ANGLE_INSTANTIATE_TEST_ES2(SamplerInStructArrayAsFunctionParameterTest);
    ANGLE_INSTANTIATE_TEST_ES2(SamplerInNestedStructAsFunctionParameterTest);
    ANGLE_INSTANTIATE_TEST_ES2(SamplerInStructAndOtherVariableTest);
    ANGLE_INSTANTIATE_TEST_ES2(TextureAnisotropyTest);
    ANGLE_INSTANTIATE_TEST_ES2(TextureBorderClampTest);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TextureBorderClampTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(TextureBorderClampTestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TextureBorderClampIntegerTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(TextureBorderClampIntegerTestES3);
    
    ANGLE_INSTANTIATE_TEST_ES2(TextureLimitsTest);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DNorm16TestES3);
    ANGLE_INSTANTIATE_TEST_ES3(Texture2DNorm16TestES3);
    
    ANGLE_INSTANTIATE_TEST(Texture2DRGTest,
                           ANGLE_ALL_TEST_PLATFORMS_ES2,
                           ANGLE_ALL_TEST_PLATFORMS_ES3,
                           ES2_EMULATE_COPY_TEX_IMAGE(),
                           ES3_EMULATE_COPY_TEX_IMAGE());
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DFloatTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(Texture2DFloatTestES3);
    
    ANGLE_INSTANTIATE_TEST_ES2(Texture2DFloatTestES2);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TextureCubeTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(TextureCubeTestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DIntegerTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(Texture2DIntegerTestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TextureCubeIntegerTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(TextureCubeIntegerTestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TextureCubeIntegerEdgeTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(TextureCubeIntegerEdgeTestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DIntegerProjectiveOffsetTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(Texture2DIntegerProjectiveOffsetTestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DArrayIntegerTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(Texture2DArrayIntegerTestES3);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture3DIntegerTestES3);
    ANGLE_INSTANTIATE_TEST_ES3(Texture3DIntegerTestES3);
    
    ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(Texture2DDepthTest);
    ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(PBOCompressedTextureTest);
    ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(ETC1CompressedTextureTest);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TextureBufferTestES31);
    ANGLE_INSTANTIATE_TEST_ES31(TextureBufferTestES31);
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CopyImageTestES31);
    ANGLE_INSTANTIATE_TEST_ES31(CopyImageTestES31);
    
    }  // anonymous namespace