Edit

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

Branch :

  • Show log

    Commit

  • Author : Geoff Lang
    Date : 2017-10-05 14:01:47
    Hash : b433e872
    Message : Change robust resource init into a context creation attribute. Enabled support on OpenGL even through the extension is not fully implemented so that testing with Chromium/Passthrough commmand decoder is still possible. BUG=angleproject:1635 Change-Id: Ia417b1779aace1eae19514325701a79cd33f4ef3 Reviewed-on: https://chromium-review.googlesource.com/678479 Commit-Queue: Geoff Lang <geofflang@chromium.org> Reviewed-by: Corentin Wallez <cwallez@chromium.org>

  • src/tests/gl_tests/RobustResourceInitTest.cpp
  • //
    // Copyright 2017 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.
    //
    // RobustResourceInitTest: Tests for GL_ANGLE_robust_resource_initialization.
    
    #include "test_utils/ANGLETest.h"
    
    #include "test_utils/gl_raii.h"
    
    namespace angle
    {
    
    // TODO(jmadill): Would be useful in a shared place in a utils folder.
    void UncompressDXTBlock(int destX,
                            int destY,
                            int destWidth,
                            const std::vector<uint8_t> &src,
                            int srcOffset,
                            GLenum format,
                            std::vector<GLColor> *colorsOut)
    {
        auto make565 = [src](int offset) {
            return static_cast<int>(src[offset + 0]) + static_cast<int>(src[offset + 1]) * 256;
        };
        auto make8888From565 = [](int c) {
            return GLColor(
                static_cast<GLubyte>(floor(static_cast<float>((c >> 11) & 0x1F) * (255.0f / 31.0f))),
                static_cast<GLubyte>(floor(static_cast<float>((c >> 5) & 0x3F) * (255.0f / 63.0f))),
                static_cast<GLubyte>(floor(static_cast<float>((c >> 0) & 0x1F) * (255.0f / 31.0f))),
                255);
        };
        auto mix = [](int mult, GLColor c0, GLColor c1, float div) {
            GLColor r = GLColor::transparentBlack;
            for (int ii = 0; ii < 4; ++ii)
            {
                r[ii] = static_cast<GLubyte>(floor(static_cast<float>(c0[ii] * mult + c1[ii]) / div));
            }
            return r;
        };
        bool isDXT1 =
            (format == GL_COMPRESSED_RGB_S3TC_DXT1_EXT) || (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT);
        int colorOffset = srcOffset + (isDXT1 ? 0 : 8);
        int color0      = make565(colorOffset + 0);
        int color1      = make565(colorOffset + 2);
        bool c0gtc1     = color0 > color1 || !isDXT1;
        GLColor rgba0   = make8888From565(color0);
        GLColor rgba1   = make8888From565(color1);
        std::array<GLColor, 4> colors = {{rgba0, rgba1,
                                          c0gtc1 ? mix(2, rgba0, rgba1, 3) : mix(1, rgba0, rgba1, 2),
                                          c0gtc1 ? mix(2, rgba1, rgba0, 3) : GLColor::black}};
    
        // Original comment preserved below for posterity:
        // "yea I know there is a lot of math in this inner loop. so sue me."
        for (int yy = 0; yy < 4; ++yy)
        {
            uint8_t pixels = src[colorOffset + 4 + yy];
            for (int xx = 0; xx < 4; ++xx)
            {
                uint8_t code     = (pixels >> (xx * 2)) & 0x3;
                GLColor srcColor = colors[code];
                uint8_t alpha    = 0;
                switch (format)
                {
                    case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
                        alpha = 255;
                        break;
                    case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
                        alpha = (code == 3 && !c0gtc1) ? 0 : 255;
                        break;
                    case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
                    {
                        uint8_t alpha0 = src[srcOffset + yy * 2 + (xx >> 1)];
                        uint8_t alpha1 = (alpha0 >> ((xx % 2) * 4)) & 0xF;
                        alpha          = alpha1 | (alpha1 << 4);
                    }
                    break;
                    case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
                    {
                        uint8_t alpha0 = src[srcOffset + 0];
                        uint8_t alpha1 = src[srcOffset + 1];
                        int alphaOff   = (yy >> 1) * 3 + 2;
                        uint32_t alphaBits =
                            static_cast<uint32_t>(src[srcOffset + alphaOff + 0]) +
                            static_cast<uint32_t>(src[srcOffset + alphaOff + 1]) * 256 +
                            static_cast<uint32_t>(src[srcOffset + alphaOff + 2]) * 65536;
                        int alphaShift    = (yy % 2) * 12 + xx * 3;
                        uint8_t alphaCode = static_cast<uint8_t>((alphaBits >> alphaShift) & 0x7);
                        if (alpha0 > alpha1)
                        {
                            switch (alphaCode)
                            {
                                case 0:
                                    alpha = alpha0;
                                    break;
                                case 1:
                                    alpha = alpha1;
                                    break;
                                default:
                                    // TODO(jmadill): fix rounding
                                    alpha = ((8 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 7;
                                    break;
                            }
                        }
                        else
                        {
                            switch (alphaCode)
                            {
                                case 0:
                                    alpha = alpha0;
                                    break;
                                case 1:
                                    alpha = alpha1;
                                    break;
                                case 6:
                                    alpha = 0;
                                    break;
                                case 7:
                                    alpha = 255;
                                    break;
                                default:
                                    // TODO(jmadill): fix rounding
                                    alpha = ((6 - alphaCode) * alpha0 + (alphaCode - 1) * alpha1) / 5;
                                    break;
                            }
                        }
                    }
                    break;
                    default:
                        ASSERT_FALSE(true);
                        break;
                }
                int dstOff           = ((destY + yy) * destWidth + destX + xx);
                (*colorsOut)[dstOff] = GLColor(srcColor[0], srcColor[1], srcColor[2], alpha);
            }
        }
    }
    
    int GetBlockSize(GLenum format)
    {
        bool isDXT1 =
            format == GL_COMPRESSED_RGB_S3TC_DXT1_EXT || format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
        return isDXT1 ? 8 : 16;
    }
    
    std::vector<GLColor> UncompressDXTIntoSubRegion(int width,
                                                    int height,
                                                    int subX0,
                                                    int subY0,
                                                    int subWidth,
                                                    int subHeight,
                                                    const std::vector<uint8_t> &data,
                                                    GLenum format)
    {
        std::vector<GLColor> dest(width * height, GLColor::transparentBlack);
    
        if ((width % 4) != 0 || (height % 4) != 0 || (subX0 % 4) != 0 || (subY0 % 4) != 0 ||
            (subWidth % 4) != 0 || (subHeight % 4) != 0)
        {
            std::cout << "Implementation error in UncompressDXTIntoSubRegion.";
            return dest;
        }
    
        int blocksAcross = subWidth / 4;
        int blocksDown   = subHeight / 4;
        int blockSize    = GetBlockSize(format);
        for (int yy = 0; yy < blocksDown; ++yy)
        {
            for (int xx = 0; xx < blocksAcross; ++xx)
            {
                UncompressDXTBlock(subX0 + xx * 4, subY0 + yy * 4, width, data,
                                   (yy * blocksAcross + xx) * blockSize, format, &dest);
            }
        }
        return dest;
    }
    
    class RobustResourceInitTest : public ANGLETest
    {
      protected:
        constexpr static int kWidth  = 128;
        constexpr static int kHeight = 128;
    
        RobustResourceInitTest()
        {
            setWindowWidth(kWidth);
            setWindowHeight(kHeight);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
    
            setRobustResourceInit(true);
        }
    
        bool hasGLExtension()
        {
            // Skip all tests on the OpenGL backend. It is not fully implemented but still needs to be
            // exposed to test in Chromium.
            if (IsDesktopOpenGL() || IsOpenGLES())
            {
                return false;
            }
    
            return extensionEnabled("GL_ANGLE_robust_resource_initialization");
        }
    
        void setupTexture(GLTexture *tex);
        void setup3DTexture(GLTexture *tex);
    
        // Checks for uninitialized (non-zero pixels) in a Texture.
        void checkNonZeroPixels(GLTexture *texture,
                                int skipX,
                                int skipY,
                                int skipWidth,
                                int skipHeight,
                                const GLColor &skip);
        void checkNonZeroPixels3D(GLTexture *texture,
                                  int skipX,
                                  int skipY,
                                  int skipWidth,
                                  int skipHeight,
                                  int textureLayer,
                                  const GLColor &skip);
        void checkFramebufferNonZeroPixels(int skipX,
                                           int skipY,
                                           int skipWidth,
                                           int skipHeight,
                                           const GLColor &skip);
    
        void checkCustomFramebufferNonZeroPixels(int fboWidth,
                                                 int fboHeight,
                                                 int skipX,
                                                 int skipY,
                                                 int skipWidth,
                                                 int skipHeight,
                                                 const GLColor &skip);
    
        const std::string kSimpleTextureVertexShader =
            "#version 300 es\n"
            "in vec4 position;\n"
            "out vec2 texcoord;\n"
            "void main()\n"
            "{\n"
            "    gl_Position = position;\n"
            "    texcoord = vec2(position.xy * 0.5 - 0.5);\n"
            "}";
    
        static std::string GetSimpleTextureFragmentShader(const char *samplerType)
        {
            std::stringstream fragmentStream;
            fragmentStream << "#version 300 es\n"
                              "precision mediump "
                           << samplerType
                           << "sampler2D;\n"
                              "precision mediump float;\n"
                              "out "
                           << samplerType
                           << "vec4 color;\n"
                              "in vec2 texcoord;\n"
                              "uniform "
                           << samplerType
                           << "sampler2D tex;\n"
                              "void main()\n"
                              "{\n"
                              "    color = texture(tex, texcoord);\n"
                              "}";
            return fragmentStream.str();
        }
    
        template <typename ClearFunc>
        void maskedDepthClear(ClearFunc clearFunc);
    
        template <typename ClearFunc>
        void maskedStencilClear(ClearFunc clearFunc);
    };
    
    class RobustResourceInitTestES3 : public RobustResourceInitTest
    {
      protected:
        template <typename PixelT>
        void testIntegerTextureInit(const char *samplerType,
                                    GLenum internalFormatRGBA,
                                    GLenum internalFormatRGB,
                                    GLenum type);
    };
    
    // Robust resource initialization is not based on hardware support or native extensions, check that
    // it only works on the implemented renderers
    TEST_P(RobustResourceInitTest, ExpectedRendererSupport)
    {
        bool shouldHaveSupport = IsD3D11() || IsD3D11_FL93() || IsD3D9();
        EXPECT_EQ(shouldHaveSupport, hasGLExtension());
    }
    
    // Tests of the GL_ROBUST_RESOURCE_INITIALIZATION_ANGLE query.
    TEST_P(RobustResourceInitTest, Queries)
    {
        // If context extension string exposed, check queries.
        if (extensionEnabled("GL_ANGLE_robust_resource_initialization"))
        {
            GLboolean enabled = 0;
            glGetBooleanv(GL_ROBUST_RESOURCE_INITIALIZATION_ANGLE, &enabled);
            EXPECT_GL_TRUE(enabled);
    
            EXPECT_GL_TRUE(glIsEnabled(GL_ROBUST_RESOURCE_INITIALIZATION_ANGLE));
            EXPECT_GL_NO_ERROR();
        }
        else
        {
            // Querying robust resource init should return INVALID_ENUM.
            GLboolean enabled = 0;
            glGetBooleanv(GL_ROBUST_RESOURCE_INITIALIZATION_ANGLE, &enabled);
            EXPECT_GL_ERROR(GL_INVALID_ENUM);
        }
    }
    
    // Tests that buffers start zero-filled if the data pointer is null.
    TEST_P(RobustResourceInitTest, BufferData)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        GLBuffer buffer;
        glBindBuffer(GL_ARRAY_BUFFER, buffer);
        glBufferData(GL_ARRAY_BUFFER, getWindowWidth() * getWindowHeight() * sizeof(GLfloat), nullptr,
                     GL_STATIC_DRAW);
    
        const std::string &vertexShader =
            "attribute vec2 position;\n"
            "attribute float testValue;\n"
            "varying vec4 colorOut;\n"
            "void main() {\n"
            "    gl_Position = vec4(position, 0, 1);\n"
            "    colorOut = testValue == 0.0 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);\n"
            "}";
        const std::string &fragmentShader =
            "varying mediump vec4 colorOut;\n"
            "void main() {\n"
            "    gl_FragColor = colorOut;\n"
            "}";
    
        ANGLE_GL_PROGRAM(program, vertexShader, fragmentShader);
    
        GLint testValueLoc = glGetAttribLocation(program.get(), "testValue");
        ASSERT_NE(-1, testValueLoc);
    
        glBindBuffer(GL_ARRAY_BUFFER, buffer);
        glVertexAttribPointer(testValueLoc, 1, GL_FLOAT, GL_FALSE, 4, nullptr);
        glEnableVertexAttribArray(testValueLoc);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    
        drawQuad(program.get(), "position", 0.5f);
    
        ASSERT_GL_NO_ERROR();
    
        std::vector<GLColor> expected(getWindowWidth() * getWindowHeight(), GLColor::green);
        std::vector<GLColor> actual(getWindowWidth() * getWindowHeight());
        glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                     actual.data());
        EXPECT_EQ(expected, actual);
    }
    
    // Regression test for passing a zero size init buffer with the extension.
    TEST_P(RobustResourceInitTest, BufferDataZeroSize)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension())
    
        GLBuffer buffer;
        glBindBuffer(GL_ARRAY_BUFFER, buffer);
        glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW);
    }
    
    // The following test code translated from WebGL 1 test:
    // https://www.khronos.org/registry/webgl/sdk/tests/conformance/misc/uninitialized-test.html
    void RobustResourceInitTest::setupTexture(GLTexture *tex)
    {
        GLuint tempTexture;
        glGenTextures(1, &tempTexture);
        glBindTexture(GL_TEXTURE_2D, tempTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
        // this can be quite undeterministic so to improve odds of seeing uninitialized data write bits
        // into tex then delete texture then re-create one with same characteristics (driver will likely
        // reuse mem) with this trick on r59046 WebKit/OSX I get FAIL 100% of the time instead of ~15%
        // of the time.
    
        std::array<uint8_t, kWidth * kHeight * 4> badData;
        for (size_t i = 0; i < badData.size(); ++i)
        {
            badData[i] = static_cast<uint8_t>(i % 255);
        }
    
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
                        badData.data());
        glDeleteTextures(1, &tempTexture);
    
        // This will create the GLTexture.
        glBindTexture(GL_TEXTURE_2D, *tex);
    }
    
    void RobustResourceInitTest::setup3DTexture(GLTexture *tex)
    {
        GLuint tempTexture;
        glGenTextures(1, &tempTexture);
        glBindTexture(GL_TEXTURE_3D, tempTexture);
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, kWidth, kHeight, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
    
        // this can be quite undeterministic so to improve odds of seeing uninitialized data write bits
        // into tex then delete texture then re-create one with same characteristics (driver will likely
        // reuse mem) with this trick on r59046 WebKit/OSX I get FAIL 100% of the time instead of ~15%
        // of the time.
    
        std::array<uint8_t, kWidth * kHeight * 2 * 4> badData;
        for (size_t i = 0; i < badData.size(); ++i)
        {
            badData[i] = static_cast<uint8_t>(i % 255);
        }
    
        glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, kWidth, kHeight, 2, GL_RGBA, GL_UNSIGNED_BYTE,
                        badData.data());
        glDeleteTextures(1, &tempTexture);
    
        // This will create the GLTexture.
        glBindTexture(GL_TEXTURE_3D, *tex);
    }
    
    void RobustResourceInitTest::checkNonZeroPixels(GLTexture *texture,
                                                    int skipX,
                                                    int skipY,
                                                    int skipWidth,
                                                    int skipHeight,
                                                    const GLColor &skip)
    {
        glBindTexture(GL_TEXTURE_2D, 0);
        GLFramebuffer fb;
        glBindFramebuffer(GL_FRAMEBUFFER, fb);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->get(), 0);
        EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        checkFramebufferNonZeroPixels(skipX, skipY, skipWidth, skipHeight, skip);
    }
    
    void RobustResourceInitTest::checkNonZeroPixels3D(GLTexture *texture,
                                                      int skipX,
                                                      int skipY,
                                                      int skipWidth,
                                                      int skipHeight,
                                                      int textureLayer,
                                                      const GLColor &skip)
    {
        glBindTexture(GL_TEXTURE_3D, 0);
        GLFramebuffer fb;
        glBindFramebuffer(GL_FRAMEBUFFER, fb);
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture->get(), 0,
                                  textureLayer);
        EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        checkFramebufferNonZeroPixels(skipX, skipY, skipWidth, skipHeight, skip);
    }
    
    void RobustResourceInitTest::checkFramebufferNonZeroPixels(int skipX,
                                                               int skipY,
                                                               int skipWidth,
                                                               int skipHeight,
                                                               const GLColor &skip)
    {
        checkCustomFramebufferNonZeroPixels(kWidth, kHeight, skipX, skipY, skipWidth, skipHeight, skip);
    }
    
    void RobustResourceInitTest::checkCustomFramebufferNonZeroPixels(int fboWidth,
                                                                     int fboHeight,
                                                                     int skipX,
                                                                     int skipY,
                                                                     int skipWidth,
                                                                     int skipHeight,
                                                                     const GLColor &skip)
    {
        std::vector<GLColor> data(fboWidth * fboHeight);
        glReadPixels(0, 0, fboWidth, fboHeight, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
    
        int k = 0;
        for (int y = 0; y < fboHeight; ++y)
        {
            for (int x = 0; x < fboWidth; ++x)
            {
                int index = (y * fboWidth + x);
                if (x >= skipX && x < skipX + skipWidth && y >= skipY && y < skipY + skipHeight)
                {
                    ASSERT_EQ(skip, data[index]);
                }
                else
                {
                    k += (data[index] != GLColor::transparentBlack) ? 1 : 0;
                }
            }
        }
    
        EXPECT_EQ(0, k);
    }
    
    // Reading an uninitialized texture (texImage2D) should succeed with all bytes set to 0.
    TEST_P(RobustResourceInitTest, ReadingUninitializedTexture)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        GLTexture tex;
        setupTexture(&tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        checkNonZeroPixels(&tex, 0, 0, 0, 0, GLColor::transparentBlack);
        EXPECT_GL_NO_ERROR();
    }
    
    // Test that calling glTexImage2D multiple times with the same size and no data resets all texture
    // data
    TEST_P(RobustResourceInitTest, ReuploadingClearsTexture)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        // Put some data into the texture
        std::array<GLColor, kWidth * kHeight> data;
        data.fill(GLColor::white);
    
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     data.data());
    
        // Reset the texture
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        checkNonZeroPixels(&tex, 0, 0, 0, 0, GLColor::transparentBlack);
        EXPECT_GL_NO_ERROR();
    }
    
    // Cover the case where null pixel data is uploaded to a texture and then sub image is used to
    // upload partial data
    TEST_P(RobustResourceInitTest, TexImageThenSubImage)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        // Put some data into the texture
    
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
        // Force the D3D texture to create a storage
        checkNonZeroPixels(&tex, 0, 0, 0, 0, GLColor::transparentBlack);
    
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
        std::array<GLColor, kWidth * kHeight> data;
        data.fill(GLColor::white);
    
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth / 2, kHeight / 2, GL_RGBA, GL_UNSIGNED_BYTE,
                        data.data());
        checkNonZeroPixels(&tex, 0, 0, kWidth / 2, kHeight / 2, GLColor::white);
        EXPECT_GL_NO_ERROR();
    }
    
    // Reading an uninitialized texture (texImage3D) should succeed with all bytes set to 0.
    TEST_P(RobustResourceInitTestES3, ReadingUninitialized3DTexture)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        GLTexture tex;
        setup3DTexture(&tex);
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, kWidth, kHeight, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
        checkNonZeroPixels3D(&tex, 0, 0, 0, 0, 0, GLColor::transparentBlack);
        EXPECT_GL_NO_ERROR();
    }
    
    // Copy of the copytexsubimage3d_texture_wrongly_initialized test that is part of the WebGL2
    // conformance suite: copy-texture-image-webgl-specific.html
    TEST_P(RobustResourceInitTestES3, CopyTexSubImage3DTextureWronglyInitialized)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        constexpr GLint kTextureLayer     = 0;
        constexpr GLint kTextureWidth     = 2;
        constexpr GLint kTextureHeight    = 2;
        constexpr GLint kTextureDepth     = 2;
        constexpr size_t kTextureDataSize = kTextureWidth * kTextureHeight * 4;
    
        GLTexture texture2D;
        glBindTexture(GL_TEXTURE_2D, texture2D);
        constexpr std::array<uint8_t, kTextureDataSize> data = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
                                                                 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
                                                                 0x0D, 0x0E, 0x0F, 0x10}};
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureWidth, kTextureHeight, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, data.data());
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture2D, 0);
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        GLTexture texture3D;
        glBindTexture(GL_TEXTURE_3D, texture3D);
        glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, kTextureWidth, kTextureHeight, kTextureDepth);
        glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, kTextureLayer, 0, 0, kTextureWidth, kTextureHeight);
    
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture3D, 0, kTextureLayer);
        std::array<uint8_t, kTextureDataSize> pixels;
        glReadPixels(0, 0, kTextureWidth, kTextureHeight, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(data, pixels);
    }
    
    // Test that binding an EGL surface to a texture does not cause it to be cleared.
    TEST_P(RobustResourceInitTestES3, BindTexImage)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        EGLWindow *window  = getEGLWindow();
        EGLSurface surface = window->getSurface();
        EGLDisplay display = window->getDisplay();
        EGLConfig config   = window->getConfig();
        EGLContext context = window->getContext();
    
        EGLint surfaceType = 0;
        eglGetConfigAttrib(display, config, EGL_SURFACE_TYPE, &surfaceType);
        if ((surfaceType & EGL_PBUFFER_BIT) == 0)
        {
            std::cout << "Test skipped because EGL config cannot be used to create pbuffers."
                      << std::endl;
            return;
        }
    
        EGLint attribs[] = {
            EGL_WIDTH,          32,
            EGL_HEIGHT,         32,
            EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
            EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,
            EGL_NONE,
        };
    
        EGLSurface pbuffer = eglCreatePbufferSurface(display, config, attribs);
        ASSERT_NE(EGL_NO_SURFACE, pbuffer);
    
        // Clear the pbuffer
        eglMakeCurrent(display, pbuffer, pbuffer, context);
        GLColor clearColor = GLColor::magenta;
        glClearColor(clearColor.R, clearColor.G, clearColor.B, clearColor.A);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_PIXEL_COLOR_EQ(0, 0, clearColor);
    
        // Bind the pbuffer to a texture and read its color
        eglMakeCurrent(display, surface, surface, context);
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        eglBindTexImage(display, pbuffer, EGL_BACK_BUFFER);
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
        EXPECT_PIXEL_COLOR_EQ(0, 0, clearColor);
    
        eglDestroySurface(display, pbuffer);
    }
    
    // Tests that drawing with an uninitialized Texture works as expected.
    TEST_P(RobustResourceInitTest, DrawWithTexture)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 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);
    
        const std::string &vertexShader =
            "attribute vec2 position;\n"
            "varying vec2 texCoord;\n"
            "void main() {\n"
            "    gl_Position = vec4(position, 0, 1);\n"
            "    texCoord = (position * 0.5) + 0.5;\n"
            "}";
        const std::string &fragmentShader =
            "precision mediump float;\n"
            "varying vec2 texCoord;\n"
            "uniform sampler2D tex;\n"
            "void main() {\n"
            "    gl_FragColor = texture2D(tex, texCoord);\n"
            "}";
    
        ANGLE_GL_PROGRAM(program, vertexShader, fragmentShader);
        drawQuad(program, "position", 0.5f);
    
        checkFramebufferNonZeroPixels(0, 0, 0, 0, GLColor::black);
    }
    
    // Reading a partially initialized texture (texImage2D) should succeed with all uninitialized bytes
    // set to 0 and initialized bytes untouched.
    TEST_P(RobustResourceInitTest, ReadingPartiallyInitializedTexture)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        GLTexture tex;
        setupTexture(&tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        GLColor data(108, 72, 36, 9);
        glTexSubImage2D(GL_TEXTURE_2D, 0, kWidth / 2, kHeight / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                        &data.R);
        checkNonZeroPixels(&tex, kWidth / 2, kHeight / 2, 1, 1, data);
        EXPECT_GL_NO_ERROR();
    }
    
    // Uninitialized parts of textures initialized via copyTexImage2D should have all bytes set to 0.
    TEST_P(RobustResourceInitTest, UninitializedPartsOfCopied2DTexturesAreBlack)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        GLTexture tex;
        setupTexture(&tex);
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        GLRenderbuffer rbo;
        glBindRenderbuffer(GL_RENDERBUFFER, rbo);
        constexpr int fboWidth  = 16;
        constexpr int fboHeight = 16;
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, fboWidth, fboHeight);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
        EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
        glClearColor(1.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_GL_NO_ERROR();
        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, kWidth, kHeight, 0);
        checkNonZeroPixels(&tex, 0, 0, fboWidth, fboHeight, GLColor::red);
        EXPECT_GL_NO_ERROR();
    }
    
    // Reading an uninitialized portion of a texture (copyTexImage2D with negative x and y) should
    // succeed with all bytes set to 0.
    TEST_P(RobustResourceInitTest, ReadingOutOfboundsCopiedTexture)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        GLTexture tex;
        setupTexture(&tex);
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        GLRenderbuffer rbo;
        glBindRenderbuffer(GL_RENDERBUFFER, rbo);
        constexpr int fboWidth  = 16;
        constexpr int fboHeight = 16;
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, fboWidth, fboHeight);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
        EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
        glClearColor(1.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_GL_NO_ERROR();
        constexpr int x = -8;
        constexpr int y = -8;
        glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, kWidth, kHeight, 0);
        checkNonZeroPixels(&tex, -x, -y, fboWidth, fboHeight, GLColor::red);
        EXPECT_GL_NO_ERROR();
    }
    
    // Tests resources are initialized properly with multisample resolve.
    TEST_P(RobustResourceInitTestES3, MultisampledDepthInitializedCorrectly)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        const std::string vs = "attribute vec4 position; void main() { gl_Position = position; }";
        const std::string fs = "void main() { gl_FragColor = vec4(1, 0, 0, 1); }";
        ANGLE_GL_PROGRAM(program, vs, fs);
    
        // Make the destination non-multisampled depth FBO.
        GLTexture color;
        glBindTexture(GL_TEXTURE_2D, color);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
        GLRenderbuffer depth;
        glBindRenderbuffer(GL_RENDERBUFFER, depth);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, kWidth, kHeight);
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        glClearColor(0, 1, 0, 1);
        glClearDepthf(0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    
        // Make the multisampled depth FBO.
        GLRenderbuffer msDepth;
        glBindRenderbuffer(GL_RENDERBUFFER, msDepth);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, kWidth, kHeight);
    
        GLFramebuffer msFBO;
        glBindFramebuffer(GL_READ_FRAMEBUFFER, msFBO);
        glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, msDepth);
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_READ_FRAMEBUFFER));
    
        // Multisample resolve.
        glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_DEPTH_BUFFER_BIT,
                          GL_NEAREST);
        ASSERT_GL_NO_ERROR();
    
        // Test drawing with the resolved depth buffer.
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glDepthMask(GL_FALSE);
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_EQUAL);
        drawQuad(program, "position", 1.0f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    }
    
    // Basic test that textures are initialized correctly.
    TEST_P(RobustResourceInitTest, Texture)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
        checkFramebufferNonZeroPixels(0, 0, 0, 0, GLColor::black);
    }
    
    template <typename PixelT>
    void RobustResourceInitTestES3::testIntegerTextureInit(const char *samplerType,
                                                           GLenum internalFormatRGBA,
                                                           GLenum internalFormatRGB,
                                                           GLenum type)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        ANGLE_GL_PROGRAM(program, kSimpleTextureVertexShader,
                         GetSimpleTextureFragmentShader(samplerType));
    
        // Make an RGBA framebuffer.
        GLTexture framebufferTexture;
        glBindTexture(GL_TEXTURE_2D, framebufferTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, internalFormatRGBA, kWidth, kHeight, 0, GL_RGBA_INTEGER, type,
                     nullptr);
        ASSERT_GL_NO_ERROR();
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, framebufferTexture,
                               0);
    
        // Make an RGB texture.
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(GL_TEXTURE_2D, 0, internalFormatRGB, kWidth, kHeight, 0, GL_RGB_INTEGER, type,
                     nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        ASSERT_GL_NO_ERROR();
    
        // Blit from the texture to the framebuffer.
        drawQuad(program, "position", 0.5f);
    
        std::array<PixelT, kWidth * kHeight * 4> data;
        glReadPixels(0, 0, kWidth, kHeight, GL_RGBA_INTEGER, type, data.data());
    
        // Check the color channels are zero and the alpha channel is 1.
        int incorrectPixels = 0;
        for (int y = 0; y < kHeight; ++y)
        {
            for (int x = 0; x < kWidth; ++x)
            {
                int index    = (y * kWidth + x) * 4;
                bool correct = (data[index] == 0 && data[index + 1] == 0 && data[index + 2] == 0 &&
                                data[index + 3] == 1);
                incorrectPixels += (!correct ? 1 : 0);
            }
        }
    
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(0, incorrectPixels);
    }
    
    // Simple tests for integer formats that ANGLE must emulate on D3D11.
    TEST_P(RobustResourceInitTestES3, TextureInit_UIntRGB8)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
        testIntegerTextureInit<uint8_t>("u", GL_RGBA8UI, GL_RGB8UI, GL_UNSIGNED_BYTE);
    }
    
    TEST_P(RobustResourceInitTestES3, TextureInit_UIntRGB32)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
        testIntegerTextureInit<uint32_t>("u", GL_RGBA32UI, GL_RGB32UI, GL_UNSIGNED_INT);
    }
    
    TEST_P(RobustResourceInitTestES3, TextureInit_IntRGB8)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
        testIntegerTextureInit<int8_t>("i", GL_RGBA8I, GL_RGB8I, GL_BYTE);
    }
    
    TEST_P(RobustResourceInitTestES3, TextureInit_IntRGB32)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
        testIntegerTextureInit<int32_t>("i", GL_RGBA32I, GL_RGB32I, GL_INT);
    }
    
    // Basic test that renderbuffers are initialized correctly.
    TEST_P(RobustResourceInitTest, Renderbuffer)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        GLRenderbuffer renderbuffer;
        glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kWidth, kHeight);
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer);
    
        checkFramebufferNonZeroPixels(0, 0, 0, 0, GLColor::black);
    }
    
    // Tests creating mipmaps with robust resource init.
    TEST_P(RobustResourceInitTestES3, GenerateMipmap)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        constexpr GLint kTextureSize = 16;
    
        // Initialize a 16x16 RGBA8 texture with no data.
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureSize, kTextureSize, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        ANGLE_GL_PROGRAM(program, kSimpleTextureVertexShader, GetSimpleTextureFragmentShader(""));
    
        // Generate mipmaps and verify all the mips.
        glGenerateMipmap(GL_TEXTURE_2D);
        ASSERT_GL_NO_ERROR();
    
        // Validate a small texture.
        glClearColor(1, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    
        // Set viewport to resize the texture and draw.
        glViewport(0, 0, 2, 2);
        drawQuad(program, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
    }
    
    // Test blitting a framebuffer out-of-bounds. Multiple iterations.
    TEST_P(RobustResourceInitTestES3, BlitFramebufferOutOfBounds)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        // Initiate data to read framebuffer
        constexpr int size                = 8;
        constexpr GLenum readbufferFormat = GL_RGBA8;
        constexpr GLenum drawbufferFormat = GL_RGBA8;
        constexpr GLenum filter           = GL_NEAREST;
    
        std::vector<GLColor> readColors(size * size, GLColor::yellow);
    
        // Create read framebuffer and feed data to read buffer
        // Read buffer may have srgb image
        GLTexture tex_read;
        glBindTexture(GL_TEXTURE_2D, tex_read);
        glTexImage2D(GL_TEXTURE_2D, 0, readbufferFormat, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     readColors.data());
    
        GLFramebuffer fbo_read;
        glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_read);
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex_read, 0);
    
        // Create draw framebuffer. Color in draw buffer is initialized to 0.
        // Draw buffer may have srgb image
        GLTexture tex_draw;
        glBindTexture(GL_TEXTURE_2D, tex_draw);
        glTexImage2D(GL_TEXTURE_2D, 0, drawbufferFormat, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
    
        GLFramebuffer fbo_draw;
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_draw);
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex_draw, 0);
    
        ASSERT_GL_NO_ERROR();
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_READ_FRAMEBUFFER));
    
        using Region = std::array<int, 4>;
    
        struct Test
        {
            constexpr Test(const Region &read, const Region &draw, const Region &real)
                : readRegion(read), drawRegion(draw), realRegion(real)
            {
            }
    
            Region readRegion;
            Region drawRegion;
            Region realRegion;
        };
    
        constexpr std::array<Test, 2> tests = {{
            // only src region is out-of-bounds, dst region has different width/height as src region.
            {{{-2, -2, 4, 4}}, {{1, 1, 4, 4}}, {{2, 2, 4, 4}}},
            // only src region is out-of-bounds, dst region has the same width/height as src region.
            {{{-2, -2, 4, 4}}, {{1, 1, 7, 7}}, {{3, 3, 7, 7}}},
        }};
    
        // Blit read framebuffer to the image in draw framebuffer.
        for (const auto &test : tests)
        {
            // both the read framebuffer and draw framebuffer bounds are [0, 0, 8, 8]
            // blitting from src region to dst region
            glBindTexture(GL_TEXTURE_2D, tex_draw);
            glTexImage2D(GL_TEXTURE_2D, 0, drawbufferFormat, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         nullptr);
    
            const auto &read = test.readRegion;
            const auto &draw = test.drawRegion;
            const auto &real = test.realRegion;
    
            glBlitFramebuffer(read[0], read[1], read[2], read[3], draw[0], draw[1], draw[2], draw[3],
                              GL_COLOR_BUFFER_BIT, filter);
    
            // Read pixels and check the correctness.
            std::vector<GLColor> pixels(size * size);
            glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_draw);
            glPixelStorei(GL_PACK_ROW_LENGTH, 0);
            glReadPixels(0, 0, size, size, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
            glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_read);
            ASSERT_GL_NO_ERROR();
    
            for (int ii = 0; ii < size; ++ii)
            {
                for (int jj = 0; jj < size; ++jj)
                {
                    GLColor expectedColor = GLColor::transparentBlack;
                    if (ii >= real[0] && ii < real[2] && jj >= real[1] && jj < real[3])
                    {
                        expectedColor = GLColor::yellow;
                    }
    
                    int loc = ii * size + jj;
                    EXPECT_EQ(expectedColor, pixels[loc]) << " at [" << jj << ", " << ii << "]";
                }
            }
        }
    }
    
    template <typename ClearFunc>
    void RobustResourceInitTest::maskedDepthClear(ClearFunc clearFunc)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        constexpr int kSize = 16;
    
        // Initialize a FBO with depth and simple color.
        GLRenderbuffer depthbuffer;
        glBindRenderbuffer(GL_RENDERBUFFER, depthbuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, kSize, kSize);
    
        GLTexture colorbuffer;
        glBindTexture(GL_TEXTURE_2D, colorbuffer);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthbuffer);
    
        ASSERT_GL_NO_ERROR();
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        // Disable depth writes and trigger a clear.
        glDepthMask(GL_FALSE);
    
        clearFunc(0.5f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
    
        // Draw red with a depth function that checks for the clear value.
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_EQUAL);
    
        const std::string vertexShader =
            "attribute vec4 position; void main() { gl_Position = position; }";
        const std::string fragmentShader = "void main() { gl_FragColor = vec4(1, 0, 0, 1); }";
        ANGLE_GL_PROGRAM(program, vertexShader, fragmentShader);
    
        drawQuad(program, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black) << "depth should not be 0.5f";
    
        drawQuad(program, "position", 1.0f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red) << "depth should be initialized to 1.0f";
    }
    
    // Test that clearing a masked depth buffer doesn't mark it clean.
    TEST_P(RobustResourceInitTest, MaskedDepthClear)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        auto clearFunc = [](float depth) {
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            glClearDepthf(depth);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        };
    
        maskedDepthClear(clearFunc);
    }
    
    // Tests the same as MaskedDepthClear, but using ClearBuffer calls.
    TEST_P(RobustResourceInitTestES3, MaskedDepthClearBuffer)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        auto clearFunc = [](float depth) {
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);
            glClearBufferfv(GL_DEPTH, 0, &depth);
        };
    
        maskedDepthClear(clearFunc);
    }
    
    template <typename ClearFunc>
    void RobustResourceInitTest::maskedStencilClear(ClearFunc clearFunc)
    {
        constexpr int kSize = 16;
    
        // Initialize a FBO with stencil and simple color.
        GLRenderbuffer stencilbuffer;
        glBindRenderbuffer(GL_RENDERBUFFER, stencilbuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, kSize, kSize);
    
        GLTexture colorbuffer;
        glBindTexture(GL_TEXTURE_2D, colorbuffer);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                                  stencilbuffer);
    
        ASSERT_GL_NO_ERROR();
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        // Disable stencil writes and trigger a clear. Use a tricky mask that does not overlap the
        // clear.
        glStencilMask(0xF0);
        clearFunc(0x0F);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
    
        // Draw red with a stencil function that checks for stencil == 0
        glEnable(GL_STENCIL_TEST);
        glStencilFunc(GL_EQUAL, 0x00, 0xFF);
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    
        const std::string vertexShader =
            "attribute vec4 position; void main() { gl_Position = position; }";
        const std::string fragmentShader = "void main() { gl_FragColor = vec4(1, 0, 0, 1); }";
        ANGLE_GL_PROGRAM(program, vertexShader, fragmentShader);
    
        drawQuad(program, "position", 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red) << "stencil should be equal to zero";
    }
    
    // Test that clearing a masked stencil buffer doesn't mark it clean.
    TEST_P(RobustResourceInitTest, MaskedStencilClear)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
        ANGLE_SKIP_TEST_IF(IsD3D11_FL93());
    
        auto clearFunc = [](GLint clearValue) {
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            glClearStencil(clearValue);
            glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        };
    
        maskedStencilClear(clearFunc);
    }
    
    // Test that clearing a masked stencil buffer doesn't mark it clean, with ClearBufferi.
    TEST_P(RobustResourceInitTestES3, MaskedStencilClearBuffer)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        auto clearFunc = [](GLint clearValue) {
            glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);
            glClearBufferiv(GL_STENCIL, 0, &clearValue);
        };
    
        maskedStencilClear(clearFunc);
    }
    
    template <int Size, typename InitializedTest>
    void VerifyRGBA8PixelRect(InitializedTest inInitialized)
    {
        std::array<std::array<GLColor, Size>, Size> actualPixels;
        glReadPixels(0, 0, Size, Size, GL_RGBA, GL_UNSIGNED_BYTE, actualPixels.data());
        ASSERT_GL_NO_ERROR();
    
        for (int y = 0; y < Size; ++y)
        {
            for (int x = 0; x < Size; ++x)
            {
                if (inInitialized(x, y))
                {
                    EXPECT_EQ(actualPixels[y][x], GLColor::red) << " at " << x << ", " << y;
                }
                else
                {
                    EXPECT_EQ(actualPixels[y][x], GLColor::transparentBlack)
                        << " at " << x << ", " << y;
                }
            }
        }
    }
    
    // Tests that calling CopyTexSubImage2D will initialize the source & destination.
    TEST_P(RobustResourceInitTest, CopyTexSubImage2D)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
        ANGLE_SKIP_TEST_IF(IsD3D11_FL93());
    
        constexpr int kDestSize = 4;
        constexpr int kSrcSize  = kDestSize / 2;
        constexpr int kOffset   = kSrcSize / 2;
    
        std::vector<GLColor> redColors(kDestSize * kDestSize, GLColor::red);
    
        // Initialize source texture with red.
        GLTexture srcTexture;
        glBindTexture(GL_TEXTURE_2D, srcTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSrcSize, kSrcSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     redColors.data());
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTexture, 0);
    
        ASSERT_GL_NO_ERROR();
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        // Create uninitialized destination texture.
        GLTexture destTexture;
        glBindTexture(GL_TEXTURE_2D, destTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kDestSize, kDestSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
    
        // Trigger the copy from initialized source into uninitialized dest.
        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kOffset, kOffset, 0, 0, kSrcSize, kSrcSize);
    
        // Verify the pixel rectangle.
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destTexture, 0);
        ASSERT_GL_NO_ERROR();
    
        auto srcInitTest = [kOffset, kDestSize](int x, int y) {
            return (x >= kOffset) && x < (kDestSize - kOffset) && (y >= kOffset) &&
                   y < (kDestSize - kOffset);
        };
    
        VerifyRGBA8PixelRect<kDestSize>(srcInitTest);
    
        // Make source texture uninitialized. Force a release by redefining a new size.
        glBindTexture(GL_TEXTURE_2D, srcTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSrcSize, kSrcSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTexture, 0);
    
        // Fill destination texture with red.
        glBindTexture(GL_TEXTURE_2D, destTexture);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kDestSize, kDestSize, GL_RGBA, GL_UNSIGNED_BYTE,
                        redColors.data());
    
        // Trigger a copy from uninitialized source into initialized dest.
        glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kOffset, kOffset, 0, 0, kSrcSize, kSrcSize);
    
        // Verify the pixel rectangle.
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destTexture, 0);
        ASSERT_GL_NO_ERROR();
    
        auto destInitTest = [srcInitTest](int x, int y) { return !srcInitTest(x, y); };
    
        VerifyRGBA8PixelRect<kDestSize>(destInitTest);
    }
    
    // Tests that calling CopyTexSubImage3D will initialize the source & destination.
    TEST_P(RobustResourceInitTestES3, CopyTexSubImage3D)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        constexpr int kDestSize = 4;
        constexpr int kSrcSize  = kDestSize / 2;
        constexpr int kOffset   = kSrcSize / 2;
    
        std::vector<GLColor> redColors(kDestSize * kDestSize * kDestSize, GLColor::red);
    
        GLTexture srcTexture;
        GLFramebuffer framebuffer;
        GLTexture destTexture;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    
        // Initialize source texture with red.
        glBindTexture(GL_TEXTURE_3D, srcTexture);
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, kSrcSize, kSrcSize, kSrcSize, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, redColors.data());
    
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, srcTexture, 0, 0);
    
        ASSERT_GL_NO_ERROR();
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        // Create uninitialized destination texture.
        glBindTexture(GL_TEXTURE_3D, destTexture);
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, kDestSize, kDestSize, kDestSize, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
    
        // Trigger the copy from initialized source into uninitialized dest.
        glCopyTexSubImage3D(GL_TEXTURE_3D, 0, kOffset, kOffset, 0, 0, 0, kSrcSize, kSrcSize);
    
        // Verify the pixel rectangle.
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, destTexture, 0, 0);
        ASSERT_GL_NO_ERROR();
    
        auto srcInitTest = [kOffset, kDestSize](int x, int y) {
            return (x >= kOffset) && x < (kDestSize - kOffset) && (y >= kOffset) &&
                   y < (kDestSize - kOffset);
        };
    
        VerifyRGBA8PixelRect<kDestSize>(srcInitTest);
    
        // Make source texture uninitialized.
        glBindTexture(GL_TEXTURE_3D, srcTexture);
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, kSrcSize, kSrcSize, kSrcSize, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, srcTexture, 0, 0);
    
        ASSERT_GL_NO_ERROR();
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        // Fill destination texture with red.
        glBindTexture(GL_TEXTURE_3D, destTexture);
        glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, kDestSize, kDestSize, kDestSize, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, redColors.data());
    
        // Trigger a copy from uninitialized source into initialized dest.
        glCopyTexSubImage3D(GL_TEXTURE_3D, 0, kOffset, kOffset, 0, 0, 0, kSrcSize, kSrcSize);
    
        // Verify the pixel rectangle.
        glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, destTexture, 0, 0);
        ASSERT_GL_NO_ERROR();
    
        auto destInitTest = [srcInitTest](int x, int y) { return !srcInitTest(x, y); };
    
        VerifyRGBA8PixelRect<kDestSize>(destInitTest);
    }
    
    // Test basic robustness with 2D array textures.
    TEST_P(RobustResourceInitTestES3, Texture2DArray)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        constexpr int kSize   = 1024;
        constexpr int kLayers = 8;
    
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
        glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, kSize, kSize, kLayers, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    
        for (int layer = 0; layer < kLayers; ++layer)
        {
            checkNonZeroPixels3D(&texture, 0, 0, 0, 0, layer, GLColor::transparentBlack);
        }
    }
    
    // Test that using TexStorage2D followed by CompressedSubImage works with robust init.
    // Taken from WebGL test conformance/extensions/webgl-compressed-texture-s3tc.
    TEST_P(RobustResourceInitTestES3, CompressedSubImage)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
        ANGLE_SKIP_TEST_IF(!extensionEnabled("GL_EXT_texture_compression_dxt1"));
    
        constexpr int width     = 8;
        constexpr int height    = 8;
        constexpr int subX0     = 0;
        constexpr int subY0     = 0;
        constexpr int subWidth  = 4;
        constexpr int subHeight = 4;
        constexpr GLenum format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
    
        static constexpr uint8_t img_8x8_rgb_dxt1[] = {
            0xe0, 0x07, 0x00, 0xf8, 0x11, 0x10, 0x15, 0x00, 0x1f, 0x00, 0xe0,
            0xff, 0x11, 0x10, 0x15, 0x00, 0xe0, 0x07, 0x1f, 0xf8, 0x44, 0x45,
            0x40, 0x55, 0x1f, 0x00, 0xff, 0x07, 0x44, 0x45, 0x40, 0x55,
        };
    
        static constexpr uint8_t img_4x4_rgb_dxt1[] = {
            0xe0, 0x07, 0x00, 0xf8, 0x11, 0x10, 0x15, 0x00,
        };
    
        std::vector<uint8_t> data(img_8x8_rgb_dxt1, img_8x8_rgb_dxt1 + ArraySize(img_8x8_rgb_dxt1));
        std::vector<uint8_t> subData(img_4x4_rgb_dxt1, img_4x4_rgb_dxt1 + ArraySize(img_4x4_rgb_dxt1));
    
        GLTexture colorbuffer;
        glBindTexture(GL_TEXTURE_2D, colorbuffer);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorbuffer, 0);
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        glViewport(0, 0, width, height);
    
        // testing format width-x-height via texStorage2D
        const auto &expectedData = UncompressDXTIntoSubRegion(width, height, subX0, subY0, subWidth,
                                                              subHeight, subData, format);
    
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D, tex);
        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);
    
        glTexStorage2D(GL_TEXTURE_2D, 1, format, width, height);
        ASSERT_GL_NO_ERROR();
        glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, subX0, subY0, subWidth, subHeight, format,
                                  static_cast<GLsizei>(subData.size()), subData.data());
        ASSERT_GL_NO_ERROR();
    
        draw2DTexturedQuad(0.5f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
    
        std::vector<GLColor> actualData(width * height);
        glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, actualData.data());
        ASSERT_GL_NO_ERROR();
    
        for (int y = 0; y < height; ++y)
        {
            for (int x = 0; x < width; ++x)
            {
                int offset                  = x + y * width;
                const GLColor expectedColor = expectedData[offset];
                const GLColor actualColor   = actualData[offset];
    
                // Allow for some minor variation because the format is compressed.
                EXPECT_NEAR(expectedColor.R, actualColor.R, 1) << " at (" << x << ", " << y << ")";
                EXPECT_NEAR(expectedColor.G, actualColor.G, 1) << " at (" << x << ", " << y << ")";
                EXPECT_NEAR(expectedColor.B, actualColor.B, 1) << " at (" << x << ", " << y << ")";
            }
        }
    }
    
    // Tests that a partial scissor still initializes contents as expected.
    TEST_P(RobustResourceInitTest, ClearWithScissor)
    {
        ANGLE_SKIP_TEST_IF(!hasGLExtension());
    
        constexpr int kSize = 16;
    
        GLRenderbuffer colorbuffer;
        glBindRenderbuffer(GL_RENDERBUFFER, colorbuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSize, kSize);
    
        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuffer);
    
        ASSERT_GL_NO_ERROR();
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    
        // Scissor to half the width.
        glEnable(GL_SCISSOR_TEST);
        glScissor(0, 0, kSize / 2, kSize);
    
        // Clear. Half the texture should be black, and half red.
        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
    
        EXPECT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::transparentBlack);
    }
    
    ANGLE_INSTANTIATE_TEST(RobustResourceInitTest,
                           ES2_D3D9(),
                           ES2_D3D11(),
                           ES3_D3D11(),
                           ES2_D3D11_FL9_3(),
                           ES2_OPENGL(),
                           ES3_OPENGL(),
                           ES2_OPENGLES(),
                           ES3_OPENGLES());
    
    ANGLE_INSTANTIATE_TEST(RobustResourceInitTestES3, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES());
    
    }  // namespace