//
// 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.
//
// EGLSurfaceTest:
//   Tests pertaining to egl::Surface.
//

#include <gtest/gtest.h>
#include "common/unsafe_buffers.h"

#include <thread>
#include <vector>

#include "common/Color.h"
#include "common/platform.h"
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
#include "util/OSWindow.h"
#include "util/Timer.h"
#include "util/test_utils.h"

#if defined(ANGLE_ENABLE_D3D11)
#    define INITGUID
#    include <guiddef.h>

#    include <d3d11.h>
#    include <dcomp.h>
#endif

using namespace angle;

namespace
{

class EGLSurfaceTest : public ANGLETest<>
{
  protected:
    EGLSurfaceTest()
        : mDisplay(EGL_NO_DISPLAY),
          mWindowSurface(EGL_NO_SURFACE),
          mPbufferSurface(EGL_NO_SURFACE),
          mContext(EGL_NO_CONTEXT),
          mSecondContext(EGL_NO_CONTEXT),
          mOSWindow(nullptr)
    {}

    void testSetUp() override
    {
        mOSWindow = OSWindow::New();
        mOSWindow->initialize("EGLSurfaceTest", 64, 64);
    }

    void tearDownContextAndSurface()
    {
        if (mDisplay == EGL_NO_DISPLAY)
        {
            return;
        }

        eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

        if (mWindowSurface != EGL_NO_SURFACE)
        {
            eglDestroySurface(mDisplay, mWindowSurface);
            mWindowSurface = EGL_NO_SURFACE;
        }

        if (mPbufferSurface != EGL_NO_SURFACE)
        {
            eglDestroySurface(mDisplay, mPbufferSurface);
            mPbufferSurface = EGL_NO_SURFACE;
        }

        if (mContext != EGL_NO_CONTEXT)
        {
            eglDestroyContext(mDisplay, mContext);
            mContext = EGL_NO_CONTEXT;
        }

        if (mSecondContext != EGL_NO_CONTEXT)
        {
            eglDestroyContext(mDisplay, mSecondContext);
            mSecondContext = EGL_NO_CONTEXT;
        }
    }

    // Release any resources created in the test body
    void testTearDown() override
    {
        tearDownContextAndSurface();

        if (mDisplay != EGL_NO_DISPLAY)
        {
            eglTerminate(mDisplay);
            mDisplay = EGL_NO_DISPLAY;
        }

        mOSWindow->destroy();
        OSWindow::Delete(&mOSWindow);

        for (OSWindow *win : mOtherWindows)
        {
            if (win != nullptr)
            {
                win->destroy();
                OSWindow::Delete(&win);
            }
        }
        mOtherWindows.clear();

        ASSERT_TRUE(mWindowSurface == EGL_NO_SURFACE && mContext == EGL_NO_CONTEXT);
    }

    void initializeDisplay()
    {
        GLenum platformType = GetParam().getRenderer();
        GLenum deviceType   = GetParam().getDeviceType();

        std::vector<EGLAttrib> displayAttributes;
        displayAttributes.push_back(EGL_PLATFORM_ANGLE_TYPE_ANGLE);
        displayAttributes.push_back(platformType);
        // Note: when the native display is Wayland, pbuffers are not supported.  Some tests only
        // need a pbuffer; if they are split into their own suite, they can use
        // GetPbufferOnlyDefaultPlatformType() instead to run when the window system is otherwise
        // Wayland.
        displayAttributes.push_back(EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE);
        displayAttributes.push_back(mOSWindow->getNativeDisplayPlatformType());
        displayAttributes.push_back(EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE);
        displayAttributes.push_back(EGL_DONT_CARE);
        displayAttributes.push_back(EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE);
        displayAttributes.push_back(EGL_DONT_CARE);
        displayAttributes.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE);
        displayAttributes.push_back(deviceType);
        displayAttributes.push_back(EGL_NONE);

        mDisplay = eglGetPlatformDisplay(GetEglPlatform(),
                                         reinterpret_cast<void *>(mOSWindow->getNativeDisplay()),
                                         displayAttributes.data());
        ASSERT_TRUE(mDisplay != EGL_NO_DISPLAY);

        EGLint majorVersion, minorVersion;
        ASSERT_TRUE(eglInitialize(mDisplay, &majorVersion, &minorVersion) == EGL_TRUE);

        eglBindAPI(EGL_OPENGL_ES_API);
        ASSERT_EGL_SUCCESS();
    }

    void initializeSingleContext(EGLContext *context, EGLint virtualizationGroup = EGL_DONT_CARE)
    {
        ASSERT_TRUE(*context == EGL_NO_CONTEXT);

        EGLint contextAttibutes[] = {EGL_CONTEXT_CLIENT_VERSION, GetParam().majorVersion,
                                     EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, virtualizationGroup,
                                     EGL_NONE};

        if (!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANGLE_context_virtualization"))
        {
            contextAttibutes[2] = EGL_NONE;
        }

        *context = eglCreateContext(mDisplay, mConfig, nullptr, contextAttibutes);
        ASSERT_EGL_SUCCESS();
        ASSERT_TRUE(*context != EGL_NO_CONTEXT);
    }

    void initializeMainContext() { initializeSingleContext(&mContext); }

    void initializeAllContexts()
    {
        initializeSingleContext(&mContext);
        initializeSingleContext(&mSecondContext);
    }

    void initializeWindowSurfaceWithAttribs(EGLConfig config,
                                            const std::vector<EGLint> &additionalAttributes,
                                            EGLenum expectedResult)
    {
        ASSERT_TRUE(mWindowSurface == EGL_NO_SURFACE);

        EGLint surfaceType = EGL_NONE;
        eglGetConfigAttrib(mDisplay, mConfig, EGL_SURFACE_TYPE, &surfaceType);

        if (surfaceType & EGL_WINDOW_BIT)
        {
            std::vector<EGLint> windowAttributes = additionalAttributes;
            windowAttributes.push_back(EGL_NONE);

            // Create first window surface
            mWindowSurface = eglCreateWindowSurface(mDisplay, mConfig, mOSWindow->getNativeWindow(),
                                                    windowAttributes.data());
        }

        ASSERT_EGLENUM_EQ(eglGetError(), expectedResult);
    }

    void initializeSurfaceWithAttribs(EGLConfig config,
                                      const std::vector<EGLint> &additionalAttributes)
    {
        mConfig = config;

        EGLint surfaceType = EGL_NONE;
        eglGetConfigAttrib(mDisplay, mConfig, EGL_SURFACE_TYPE, &surfaceType);

        if (surfaceType & EGL_WINDOW_BIT)
        {
            initializeWindowSurfaceWithAttribs(config, additionalAttributes, EGL_SUCCESS);
        }

        if (surfaceType & EGL_PBUFFER_BIT)
        {
            ASSERT_TRUE(mPbufferSurface == EGL_NO_SURFACE);

            std::vector<EGLint> pbufferAttributes = additionalAttributes;

            // Give pbuffer non-zero dimensions.
            pbufferAttributes.push_back(EGL_WIDTH);
            pbufferAttributes.push_back(64);
            pbufferAttributes.push_back(EGL_HEIGHT);
            pbufferAttributes.push_back(64);
            pbufferAttributes.push_back(EGL_NONE);

            mPbufferSurface = eglCreatePbufferSurface(mDisplay, mConfig, pbufferAttributes.data());
            ASSERT_EGL_SUCCESS();
        }
    }

    void initializeSurface(EGLConfig config)
    {
        std::vector<EGLint> additionalAttributes;
        initializeSurfaceWithAttribs(config, additionalAttributes);
    }

    EGLConfig chooseDefaultConfig(bool requireWindowSurface) const
    {
        const EGLint configAttributes[] = {EGL_RED_SIZE,
                                           EGL_DONT_CARE,
                                           EGL_GREEN_SIZE,
                                           EGL_DONT_CARE,
                                           EGL_BLUE_SIZE,
                                           EGL_DONT_CARE,
                                           EGL_ALPHA_SIZE,
                                           EGL_DONT_CARE,
                                           EGL_DEPTH_SIZE,
                                           EGL_DONT_CARE,
                                           EGL_STENCIL_SIZE,
                                           EGL_DONT_CARE,
                                           EGL_SAMPLE_BUFFERS,
                                           0,
                                           EGL_SURFACE_TYPE,
                                           requireWindowSurface ? EGL_WINDOW_BIT : EGL_DONT_CARE,
                                           EGL_NONE};

        EGLint configCount;
        EGLConfig config;
        if (eglChooseConfig(mDisplay, configAttributes, &config, 1, &configCount) != EGL_TRUE)
            return nullptr;
        if (configCount != 1)
            return nullptr;
        return config;
    }

    void initializeSurfaceWithDefaultConfig(bool requireWindowSurface)
    {
        EGLConfig defaultConfig = chooseDefaultConfig(requireWindowSurface);
        ASSERT_NE(defaultConfig, nullptr);
        initializeSurface(defaultConfig);
    }

    GLuint createProgram(const char *fs = angle::essl1_shaders::fs::Red())
    {
        return CompileProgram(angle::essl1_shaders::vs::Simple(), fs);
    }

    void drawWithProgram(GLuint program)
    {
        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);

        GLint positionLocation =
            glGetAttribLocation(program, angle::essl1_shaders::PositionAttrib());

        glUseProgram(program);

        const GLfloat vertices[] = {
            -1.0f, 1.0f, 0.5f, -1.0f, -1.0f, 0.5f, 1.0f, -1.0f, 0.5f,

            -1.0f, 1.0f, 0.5f, 1.0f,  -1.0f, 0.5f, 1.0f, 1.0f,  0.5f,
        };

        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices);
        glEnableVertexAttribArray(positionLocation);

        glDrawArrays(GL_TRIANGLES, 0, 6);

        glDisableVertexAttribArray(positionLocation);
        glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);

        EXPECT_PIXEL_EQ(mOSWindow->getWidth() / 2, mOSWindow->getHeight() / 2, 255, 0, 0, 255);
    }

    void runMessageLoopTest(EGLSurface secondSurface, EGLContext secondContext)
    {
        eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
        ASSERT_EGL_SUCCESS();

        // Make a second context current
        eglMakeCurrent(mDisplay, secondSurface, secondSurface, secondContext);
        eglDestroySurface(mDisplay, mWindowSurface);

        // Create second window surface
        std::vector<EGLint> surfaceAttributes;
        surfaceAttributes.push_back(EGL_NONE);
        surfaceAttributes.push_back(EGL_NONE);

        mWindowSurface = eglCreateWindowSurface(mDisplay, mConfig, mOSWindow->getNativeWindow(),
                                                &surfaceAttributes[0]);
        ASSERT_EGL_SUCCESS();

        eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
        ASSERT_EGL_SUCCESS();

        mOSWindow->signalTestEvent();
        mOSWindow->messageLoop();
        ASSERT_TRUE(mOSWindow->didTestEventFire());

        // Simple operation to test the FBO is set appropriately
        glClear(GL_COLOR_BUFFER_BIT);
    }

    void runWaitSemaphoreTest(bool useSecondContext);
    void runDestroyNotCurrentSurfaceTest(bool testWindowsSurface);

    void drawQuadThenTearDown();

    int drawSizeCheckRect(EGLSurface surface, GLsizei checkRectWidth, GLsizei checkRectHeight);

    EGLDisplay mDisplay;
    EGLSurface mWindowSurface;
    EGLSurface mPbufferSurface;
    EGLContext mContext;
    EGLContext mSecondContext;
    EGLConfig mConfig;
    OSWindow *mOSWindow;
    std::vector<OSWindow *> mOtherWindows;
};

class EGLFloatSurfaceTest : public EGLSurfaceTest
{
  protected:
    EGLFloatSurfaceTest() : EGLSurfaceTest()
    {
        setWindowWidth(512);
        setWindowHeight(512);
    }

    void testSetUp() override
    {
        mOSWindow = OSWindow::New();
        mOSWindow->initialize("EGLFloatSurfaceTest", 64, 64);
    }

    void testTearDown() override
    {
        EGLSurfaceTest::testTearDown();
        glDeleteProgram(mProgram);
    }

    GLuint createProgram()
    {
        constexpr char kFS[] =
            "precision highp float;\n"
            "void main()\n"
            "{\n"
            "   gl_FragColor = vec4(1.0, 2.0, 3.0, 4.0);\n"
            "}\n";
        return CompileProgram(angle::essl1_shaders::vs::Simple(), kFS);
    }

    bool initializeSurfaceWithFloatConfig()
    {
        const EGLint configAttributes[] = {EGL_SURFACE_TYPE,
                                           EGL_WINDOW_BIT,
                                           EGL_RED_SIZE,
                                           16,
                                           EGL_GREEN_SIZE,
                                           16,
                                           EGL_BLUE_SIZE,
                                           16,
                                           EGL_ALPHA_SIZE,
                                           16,
                                           EGL_COLOR_COMPONENT_TYPE_EXT,
                                           EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT,
                                           EGL_NONE,
                                           EGL_NONE};

        initializeDisplay();
        EGLConfig config;
        if (EGLWindow::FindEGLConfig(mDisplay, configAttributes, &config) == EGL_FALSE)
        {
            std::cout << "EGLConfig for a float surface is not supported, skipping test"
                      << std::endl;
            return false;
        }

        initializeSurface(config);
        initializeMainContext();

        eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
        mProgram = createProgram();
        return true;
    }

    GLuint mProgram;
};

class EGLSingleBufferTest : public ANGLETest<>
{
  protected:
    EGLSingleBufferTest() {}

    void testSetUp() override
    {
        mOSWindow = OSWindow::New();
        mOSWindow->initialize("EGLSingleBufferTest", kWidth, kHeight);

        EGLAttrib dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(),
                                 EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE,
                                 static_cast<EGLAttrib>(mOSWindow->getNativeDisplayPlatformType()),
                                 EGL_NONE};
        mDisplay              = eglGetPlatformDisplay(GetEglPlatform(),
                                                      reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
        ASSERT_TRUE(mDisplay != EGL_NO_DISPLAY);
        ASSERT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr));
        mMajorVersion = GetParam().majorVersion;
    }

    void testTearDown() override
    {
        mOSWindow->destroy();
        OSWindow::Delete(&mOSWindow);

        eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        eglTerminate(mDisplay);
    }

    bool chooseConfig(EGLConfig *config, bool mutableRenderBuffer) const
    {
        bool result          = false;
        EGLint count         = 0;
        EGLint clientVersion = mMajorVersion == 3 ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT;
        EGLint attribs[]     = {
            EGL_RED_SIZE,
            8,
            EGL_GREEN_SIZE,
            8,
            EGL_BLUE_SIZE,
            8,
            EGL_ALPHA_SIZE,
            0,
            EGL_RENDERABLE_TYPE,
            clientVersion,
            EGL_SURFACE_TYPE,
            EGL_WINDOW_BIT | (mutableRenderBuffer ? EGL_MUTABLE_RENDER_BUFFER_BIT_KHR : 0),
            EGL_NONE};

        result = eglChooseConfig(mDisplay, attribs, config, 1, &count);
        return result && (count > 0);
    }

    bool createContext(EGLConfig config, EGLContext *context)
    {
        EXPECT_TRUE(*context == EGL_NO_CONTEXT);

        bool result      = false;
        EGLint attribs[] = {EGL_CONTEXT_MAJOR_VERSION, mMajorVersion, EGL_NONE};

        *context = eglCreateContext(mDisplay, config, nullptr, attribs);
        result   = (*context != EGL_NO_CONTEXT);
        EXPECT_TRUE(result);
        return result;
    }

    bool createWindowSurface(EGLConfig config,
                             EGLNativeWindowType win,
                             EGLSurface *surface,
                             EGLint renderBuffer) const
    {
        EXPECT_TRUE(*surface == EGL_NO_SURFACE);

        bool result      = false;
        EGLint attribs[] = {EGL_RENDER_BUFFER, renderBuffer, EGL_NONE};

        *surface = eglCreateWindowSurface(mDisplay, config, win, attribs);
        result   = (*surface != EGL_NO_SURFACE);
        EXPECT_TRUE(result);
        return result;
    }

    uint32_t drawAndSwap(EGLSurface &surface, EGLDisplay &display, uint32_t color, bool flush);

    OSWindow *mOSWindow  = nullptr;
    EGLDisplay mDisplay  = EGL_NO_DISPLAY;
    EGLint mMajorVersion = 0;
    const EGLint kWidth  = 32;
    const EGLint kHeight = 32;
};

class EGLAndroidAutoRefreshTest : public EGLSingleBufferTest
{};

// Test clearing and checking the color is correct
TEST_P(EGLFloatSurfaceTest, Clearing)
{
    ANGLE_SKIP_TEST_IF(!initializeSurfaceWithFloatConfig());

    ASSERT_NE(0u, mProgram) << "shader compilation failed.";
    ASSERT_GL_NO_ERROR();

    GLColor32F clearColor(0.0f, 1.0f, 2.0f, 3.0f);
    glClearColor(clearColor.R, clearColor.G, clearColor.B, clearColor.A);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR32F_EQ(0, 0, clearColor);
}

// Test drawing and checking the color is correct
TEST_P(EGLFloatSurfaceTest, Drawing)
{
    ANGLE_SKIP_TEST_IF(!initializeSurfaceWithFloatConfig());

    ASSERT_NE(0u, mProgram) << "shader compilation failed.";
    ASSERT_GL_NO_ERROR();

    glUseProgram(mProgram);
    drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f);

    EXPECT_PIXEL_32F_EQ(0, 0, 1.0f, 2.0f, 3.0f, 4.0f);
}

class EGLSurfaceTest3 : public EGLSurfaceTest
{};

// Test a surface bug where we could have two Window surfaces active
// at one time, blocking message loops. See http://crbug.com/475085
TEST_P(EGLSurfaceTest, MessageLoopBug)
{
    // http://anglebug.com/42261801
    ANGLE_SKIP_TEST_IF(IsAndroid());

    // http://anglebug.com/42261815
    ANGLE_SKIP_TEST_IF(IsOzone());

    // http://anglebug.com/42264022
    ANGLE_SKIP_TEST_IF(IsIOS());

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();

    runMessageLoopTest(EGL_NO_SURFACE, EGL_NO_CONTEXT);
}

// Tests the message loop bug, but with setting a second context
// instead of null.
TEST_P(EGLSurfaceTest, MessageLoopBugContext)
{
    // http://anglebug.com/42261801
    ANGLE_SKIP_TEST_IF(IsAndroid());

    // http://anglebug.com/42261815
    ANGLE_SKIP_TEST_IF(IsOzone());

    // http://anglebug.com/42264022
    ANGLE_SKIP_TEST_IF(IsIOS());

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeAllContexts();

    ANGLE_SKIP_TEST_IF(!mPbufferSurface);
    runMessageLoopTest(mPbufferSurface, mSecondContext);
}

// Test a bug where calling makeCurrent twice would release the surface
TEST_P(EGLSurfaceTest, MakeCurrentTwice)
{
    initializeDisplay();
    initializeSurfaceWithDefaultConfig(false);
    initializeMainContext();

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // Simple operation to test the FBO is set appropriately
    glClear(GL_COLOR_BUFFER_BIT);
}

// Test that we dont crash during a clear when specified scissor is outside render area
// due to reducing window size.
TEST_P(EGLSurfaceTest, ShrinkWindowThenScissoredClear)
{
    initializeDisplay();
    initializeSurfaceWithDefaultConfig(false);
    initializeMainContext();

    // Create 64x64 window and make it current
    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // Resize window to 32x32
    mOSWindow->resize(32, 32);

    // Perform empty swap
    eglSwapBuffers(mDisplay, mWindowSurface);

    // Enable scissor test
    glEnable(GL_SCISSOR_TEST);
    ASSERT_GL_NO_ERROR();

    // Set scissor to (50, 50, 10, 10)
    glScissor(50, 50, 10, 10);
    ASSERT_GL_NO_ERROR();

    // Clear to specific color
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Disable scissor test
    glDisable(GL_SCISSOR_TEST);
    ASSERT_GL_NO_ERROR();
}

// Test that we dont early return from a clear when specified scissor is outside render area
// before increasing window size.
TEST_P(EGLSurfaceTest, GrowWindowThenScissoredClear)
{
    initializeDisplay();
    initializeSurfaceWithDefaultConfig(false);
    initializeMainContext();

    // Create 64x64 window and make it current
    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // Resize window to 128x128
    mOSWindow->resize(128, 128);

    // Perform empty swap
    eglSwapBuffers(mDisplay, mWindowSurface);

    // Enable scissor test
    glEnable(GL_SCISSOR_TEST);
    ASSERT_GL_NO_ERROR();

    // Set scissor to (64, 64, 10, 10)
    glScissor(64, 64, 10, 10);
    ASSERT_GL_NO_ERROR();

    // Clear to specific color
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Disable scissor test
    glDisable(GL_SCISSOR_TEST);
    ASSERT_GL_NO_ERROR();

    EXPECT_PIXEL_RECT_EQ(64, 64, 10, 10, GLColor::blue);
    ASSERT_GL_NO_ERROR();
}

// Test that just a ClearBuffer* with an invalid scissor doesn't cause an assert.
TEST_P(EGLSurfaceTest3, ShrinkWindowThenScissoredClearBuffer)
{
    initializeDisplay();
    initializeSurfaceWithDefaultConfig(false);
    initializeMainContext();

    // Create 64x64 window and make it current
    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // Resize window to 32x32
    mOSWindow->resize(32, 32);

    // Perform empty swap
    eglSwapBuffers(mDisplay, mWindowSurface);

    // Enable scissor test
    glEnable(GL_SCISSOR_TEST);
    ASSERT_GL_NO_ERROR();

    // Set scissor to (50, 50, 10, 10)
    glScissor(50, 50, 10, 10);
    ASSERT_GL_NO_ERROR();

    std::vector<GLint> testInt(4);
    glClearBufferiv(GL_COLOR, 0, testInt.data());
    std::vector<GLuint> testUint(4);
    glClearBufferuiv(GL_COLOR, 0, testUint.data());
    std::vector<GLfloat> testFloat(4);
    glClearBufferfv(GL_COLOR, 0, testFloat.data());

    // Disable scissor test
    glDisable(GL_SCISSOR_TEST);
    ASSERT_GL_NO_ERROR();
}

// This is a regression test to verify that surfaces are not prematurely destroyed.
TEST_P(EGLSurfaceTest, SurfaceUseAfterFreeBug)
{
    initializeDisplay();

    // Initialize an RGBA8 window and pbuffer surface
    constexpr EGLint kSurfaceAttributes[] = {EGL_RED_SIZE,     8,
                                             EGL_GREEN_SIZE,   8,
                                             EGL_BLUE_SIZE,    8,
                                             EGL_ALPHA_SIZE,   8,
                                             EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
                                             EGL_NONE};

    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;
    ASSERT_EGL_TRUE(eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ASSERT_NE(configCount, 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);
    initializeAllContexts();
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);
    ASSERT_NE(mPbufferSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mSecondContext);
    ASSERT_EGL_SUCCESS();
    glClear(GL_COLOR_BUFFER_BIT);

    eglMakeCurrent(mDisplay, mPbufferSurface, mPbufferSurface, mContext);
    ASSERT_EGL_SUCCESS();
    glClear(GL_COLOR_BUFFER_BIT);

    eglDestroySurface(mDisplay, mPbufferSurface);
    ASSERT_EGL_SUCCESS();
    mPbufferSurface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, mSecondContext);
    ASSERT_EGL_SUCCESS();
    mSecondContext = EGL_NO_CONTEXT;
}

// Test that the window can be reset repeatedly before surface creation.
TEST_P(EGLSurfaceTest, ResetNativeWindow)
{
    setWindowVisible(mOSWindow, true);

    initializeDisplay();

    for (int i = 0; i < 10; ++i)
    {
        mOSWindow->resetNativeWindow();
    }

    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);

    eglSwapBuffers(mDisplay, mWindowSurface);
    ASSERT_EGL_SUCCESS();
}

// Test swap buffer without any draw calls.
TEST_P(EGLSurfaceTest, SwapWithoutAnyDraw)
{
    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    for (int i = 0; i < 10; ++i)
    {
        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();
    }
}

// Test creating a surface that supports a EGLConfig with 16bit
// support GL_RGB565
TEST_P(EGLSurfaceTest, CreateWithEGLConfig5650Support)
{
    const EGLint configAttributes[] = {EGL_SURFACE_TYPE,
                                       EGL_WINDOW_BIT,
                                       EGL_RED_SIZE,
                                       5,
                                       EGL_GREEN_SIZE,
                                       6,
                                       EGL_BLUE_SIZE,
                                       5,
                                       EGL_ALPHA_SIZE,
                                       0,
                                       EGL_DEPTH_SIZE,
                                       0,
                                       EGL_STENCIL_SIZE,
                                       0,
                                       EGL_SAMPLE_BUFFERS,
                                       0,
                                       EGL_NONE};

    initializeDisplay();
    EGLConfig config;
    if (EGLWindow::FindEGLConfig(mDisplay, configAttributes, &config) == EGL_FALSE)
    {
        std::cout << "EGLConfig for a GL_RGB565 surface is not supported, skipping test"
                  << std::endl;
        return;
    }

    initializeSurface(config);
    initializeMainContext();

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    GLuint program = createProgram();
    ASSERT_NE(0u, program);
    drawWithProgram(program);
    EXPECT_GL_NO_ERROR();
    glDeleteProgram(program);
}

// Test creating a surface that supports a EGLConfig with 16bit
// support GL_RGBA4
TEST_P(EGLSurfaceTest, CreateWithEGLConfig4444Support)
{
    const EGLint configAttributes[] = {EGL_SURFACE_TYPE,
                                       EGL_WINDOW_BIT,
                                       EGL_RED_SIZE,
                                       4,
                                       EGL_GREEN_SIZE,
                                       4,
                                       EGL_BLUE_SIZE,
                                       4,
                                       EGL_ALPHA_SIZE,
                                       4,
                                       EGL_DEPTH_SIZE,
                                       0,
                                       EGL_STENCIL_SIZE,
                                       0,
                                       EGL_SAMPLE_BUFFERS,
                                       0,
                                       EGL_NONE};

    initializeDisplay();
    EGLConfig config;
    if (EGLWindow::FindEGLConfig(mDisplay, configAttributes, &config) == EGL_FALSE)
    {
        std::cout << "EGLConfig for a GL_RGBA4 surface is not supported, skipping test"
                  << std::endl;
        return;
    }

    initializeSurface(config);
    initializeMainContext();

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    GLuint program = createProgram();
    ASSERT_NE(0u, program);
    drawWithProgram(program);
    EXPECT_GL_NO_ERROR();
    glDeleteProgram(program);
}

// Test creating a surface that supports a EGLConfig with 16bit
// support GL_RGB5_A1
TEST_P(EGLSurfaceTest, CreateWithEGLConfig5551Support)
{
    const EGLint configAttributes[] = {EGL_SURFACE_TYPE,
                                       EGL_WINDOW_BIT,
                                       EGL_RED_SIZE,
                                       5,
                                       EGL_GREEN_SIZE,
                                       5,
                                       EGL_BLUE_SIZE,
                                       5,
                                       EGL_ALPHA_SIZE,
                                       1,
                                       EGL_DEPTH_SIZE,
                                       0,
                                       EGL_STENCIL_SIZE,
                                       0,
                                       EGL_SAMPLE_BUFFERS,
                                       0,
                                       EGL_NONE};

    initializeDisplay();
    EGLConfig config;
    if (EGLWindow::FindEGLConfig(mDisplay, configAttributes, &config) == EGL_FALSE)
    {
        std::cout << "EGLConfig for a GL_RGB5_A1 surface is not supported, skipping test"
                  << std::endl;
        return;
    }

    initializeSurface(config);
    initializeMainContext();

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    GLuint program = createProgram();
    ASSERT_NE(0u, program);
    drawWithProgram(program);
    EXPECT_GL_NO_ERROR();
    glDeleteProgram(program);
}

// Test creating a surface that supports a EGLConfig without alpha support
TEST_P(EGLSurfaceTest, CreateWithEGLConfig8880Support)
{
    const EGLint configAttributes[] = {EGL_SURFACE_TYPE,
                                       EGL_WINDOW_BIT,
                                       EGL_RED_SIZE,
                                       8,
                                       EGL_GREEN_SIZE,
                                       8,
                                       EGL_BLUE_SIZE,
                                       8,
                                       EGL_ALPHA_SIZE,
                                       0,
                                       EGL_DEPTH_SIZE,
                                       0,
                                       EGL_STENCIL_SIZE,
                                       0,
                                       EGL_SAMPLE_BUFFERS,
                                       0,
                                       EGL_NONE};

    initializeDisplay();
    EGLConfig config;
    if (EGLWindow::FindEGLConfig(mDisplay, configAttributes, &config) == EGL_FALSE)
    {
        std::cout << "EGLConfig for a GL_RGB8_OES surface is not supported, skipping test"
                  << std::endl;
        return;
    }

    initializeSurface(config);
    initializeMainContext();

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    GLuint program = createProgram();
    ASSERT_NE(0u, program);
    drawWithProgram(program);
    EXPECT_GL_NO_ERROR();
    glDeleteProgram(program);
}

// Test creating a surface that supports GL_RGB10_A2 with BT2020 colorspaces
TEST_P(EGLSurfaceTest, CreateWithEGLConfig1010102Support)
{
    const EGLint configAttributes[] = {EGL_SURFACE_TYPE,
                                       EGL_WINDOW_BIT,
                                       EGL_RED_SIZE,
                                       10,
                                       EGL_GREEN_SIZE,
                                       10,
                                       EGL_BLUE_SIZE,
                                       10,
                                       EGL_ALPHA_SIZE,
                                       2,
                                       EGL_DEPTH_SIZE,
                                       0,
                                       EGL_STENCIL_SIZE,
                                       0,
                                       EGL_SAMPLE_BUFFERS,
                                       0,
                                       EGL_NONE};

    initializeDisplay();
    ASSERT_NE(mDisplay, EGL_NO_DISPLAY);

    if (EGLWindow::FindEGLConfig(mDisplay, configAttributes, &mConfig) == EGL_FALSE)
    {
        std::cout << "EGLConfig for a GL_RGB10_A2 surface is not supported, skipping test"
                  << std::endl;
        return;
    }

    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_gl_colorspace_bt2020_hlg"));
    ANGLE_SKIP_TEST_IF(
        !IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_gl_colorspace_bt2020_linear"));
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_gl_colorspace_bt2020_pq"));

    initializeMainContext();
    ASSERT_NE(mContext, EGL_NO_CONTEXT);

    constexpr std::array<EGLint, 3u> kBt2020Colorspaces = {EGL_GL_COLORSPACE_BT2020_HLG_EXT,
                                                           EGL_GL_COLORSPACE_BT2020_LINEAR_EXT,
                                                           EGL_GL_COLORSPACE_BT2020_PQ_EXT};
    for (EGLint bt2020Colorspace : kBt2020Colorspaces)
    {
        std::vector<EGLint> winSurfaceAttribs;
        winSurfaceAttribs.push_back(EGL_GL_COLORSPACE_KHR);
        winSurfaceAttribs.push_back(bt2020Colorspace);

        initializeWindowSurfaceWithAttribs(mConfig, winSurfaceAttribs, EGL_SUCCESS);
        ASSERT_EGL_SUCCESS();
        ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

        EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext));
        ASSERT_EGL_SUCCESS();

        GLuint program = createProgram();
        ASSERT_NE(0u, program);
        drawWithProgram(program);
        EXPECT_GL_NO_ERROR();
        glDeleteProgram(program);

        eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        eglDestroySurface(mDisplay, mWindowSurface);
        mWindowSurface = EGL_NO_SURFACE;
    }
}

TEST_P(EGLSurfaceTest, FixedSizeWindow)
{
    const EGLint configAttributes[] = {EGL_SURFACE_TYPE,
                                       EGL_WINDOW_BIT,
                                       EGL_RED_SIZE,
                                       8,
                                       EGL_GREEN_SIZE,
                                       8,
                                       EGL_BLUE_SIZE,
                                       8,
                                       EGL_ALPHA_SIZE,
                                       0,
                                       EGL_DEPTH_SIZE,
                                       0,
                                       EGL_STENCIL_SIZE,
                                       0,
                                       EGL_SAMPLE_BUFFERS,
                                       0,
                                       EGL_NONE};

    initializeDisplay();
    ANGLE_SKIP_TEST_IF(EGLWindow::FindEGLConfig(mDisplay, configAttributes, &mConfig) == EGL_FALSE);

    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANGLE_window_fixed_size"));

    constexpr EGLint kInitialSize = 64;
    constexpr EGLint kUpdateSize  = 32;

    EGLint surfaceAttributes[] = {
        EGL_FIXED_SIZE_ANGLE, EGL_TRUE, EGL_WIDTH, kInitialSize, EGL_HEIGHT, kInitialSize, EGL_NONE,
    };

    // Create first window surface
    mWindowSurface =
        eglCreateWindowSurface(mDisplay, mConfig, mOSWindow->getNativeWindow(), surfaceAttributes);
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(EGL_NO_SURFACE, mWindowSurface);

    initializeMainContext();
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext));
    ASSERT_EGL_SUCCESS();

    EGLint queryIsFixedSize = 0;
    EXPECT_EGL_TRUE(
        eglQuerySurface(mDisplay, mWindowSurface, EGL_FIXED_SIZE_ANGLE, &queryIsFixedSize));
    ASSERT_EGL_SUCCESS();
    EXPECT_EGL_TRUE(queryIsFixedSize);

    EGLint queryWidth = 0;
    EXPECT_EGL_TRUE(eglQuerySurface(mDisplay, mWindowSurface, EGL_WIDTH, &queryWidth));
    ASSERT_EGL_SUCCESS();
    EXPECT_EQ(kInitialSize, queryWidth);

    EGLint queryHeight = 0;
    EXPECT_EGL_TRUE(eglQuerySurface(mDisplay, mWindowSurface, EGL_HEIGHT, &queryHeight));
    ASSERT_EGL_SUCCESS();
    EXPECT_EQ(kInitialSize, queryHeight);

    // Update the size
    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, mWindowSurface, EGL_WIDTH, kUpdateSize));
    ASSERT_EGL_SUCCESS();

    EXPECT_EGL_TRUE(eglWaitNative(EGL_CORE_NATIVE_ENGINE));
    ASSERT_EGL_SUCCESS();

    EGLint queryUpdatedWidth = 0;
    EXPECT_EGL_TRUE(eglQuerySurface(mDisplay, mWindowSurface, EGL_WIDTH, &queryUpdatedWidth));
    ASSERT_EGL_SUCCESS();
    EXPECT_EQ(kUpdateSize, queryUpdatedWidth);
}

TEST_P(EGLSurfaceTest3, MakeCurrentDifferentSurfaces)
{
    const EGLint configAttributes[] = {
        EGL_RED_SIZE,   8, EGL_GREEN_SIZE,   8, EGL_BLUE_SIZE,      8, EGL_ALPHA_SIZE, 8,
        EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, 0, EGL_SAMPLE_BUFFERS, 0, EGL_NONE};
    EGLSurface firstPbufferSurface;
    EGLSurface secondPbufferSurface;

    initializeDisplay();
    ANGLE_SKIP_TEST_IF(EGLWindow::FindEGLConfig(mDisplay, configAttributes, &mConfig) == EGL_FALSE);

    EGLint surfaceType = 0;
    eglGetConfigAttrib(mDisplay, mConfig, EGL_SURFACE_TYPE, &surfaceType);
    bool supportsPbuffers    = (surfaceType & EGL_PBUFFER_BIT) != 0;
    EGLint bindToTextureRGBA = 0;
    eglGetConfigAttrib(mDisplay, mConfig, EGL_BIND_TO_TEXTURE_RGBA, &bindToTextureRGBA);
    bool supportsBindTexImage = (bindToTextureRGBA == EGL_TRUE);

    const EGLint pBufferAttributes[] = {
        EGL_WIDTH,          64,
        EGL_HEIGHT,         64,
        EGL_TEXTURE_FORMAT, supportsPbuffers ? EGL_TEXTURE_RGBA : EGL_NO_TEXTURE,
        EGL_TEXTURE_TARGET, supportsBindTexImage ? EGL_TEXTURE_2D : EGL_NO_TEXTURE,
        EGL_NONE,           EGL_NONE,
    };

    // Create the surfaces
    firstPbufferSurface = eglCreatePbufferSurface(mDisplay, mConfig, pBufferAttributes);
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(EGL_NO_SURFACE, firstPbufferSurface);
    secondPbufferSurface = eglCreatePbufferSurface(mDisplay, mConfig, pBufferAttributes);
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(EGL_NO_SURFACE, secondPbufferSurface);

    initializeMainContext();

    // Use the same surface for both draw and read
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, firstPbufferSurface, firstPbufferSurface, mContext));

    // TODO(http://anglebug.com/42264803): Failing with OpenGL ES backend on Android.
    // Must be after the eglMakeCurrent() so the renderer string is initialized.
    ANGLE_SKIP_TEST_IF(IsOpenGLES() && IsAndroid());

    glClearColor(kFloatRed.R, kFloatRed.G, kFloatRed.B, kFloatRed.A);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Use different surfaces for draw and read, read should stay the same
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, secondPbufferSurface, firstPbufferSurface, mContext));
    glClearColor(kFloatBlue.R, kFloatBlue.G, kFloatBlue.B, kFloatBlue.A);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    // Verify draw surface was cleared
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, secondPbufferSurface, secondPbufferSurface, mContext));
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, firstPbufferSurface, secondPbufferSurface, mContext));
    ASSERT_EGL_SUCCESS();

    // Blit the source surface to the destination surface
    glBlitFramebuffer(0, 0, 64, 64, 0, 0, 64, 64, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    ASSERT_GL_NO_ERROR();
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, firstPbufferSurface, firstPbufferSurface, mContext));
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
}

#if defined(ANGLE_ENABLE_D3D11)
class EGLSurfaceTestD3D11 : public EGLSurfaceTest
{
  protected:
    // offset - draw into the texture at offset (|offset|, |offset|)
    // pix25 - the expected pixel value at (25, 25)
    // pix75 - the expected pixel value at (75, 75)
    void testTextureOffset(int offset, UINT pix25, UINT pix75)
    {
        initializeDisplay();

        const EGLint configAttributes[] = {
            EGL_RED_SIZE,   8, EGL_GREEN_SIZE,   8, EGL_BLUE_SIZE,      8, EGL_ALPHA_SIZE, 8,
            EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, 0, EGL_SAMPLE_BUFFERS, 0, EGL_NONE};

        EGLConfig config;
        ASSERT_EGL_TRUE(EGLWindow::FindEGLConfig(mDisplay, configAttributes, &config));

        mConfig = config;
        initializeMainContext();

        EGLAttrib device       = 0;
        EGLAttrib newEglDevice = 0;
        ASSERT_EGL_TRUE(eglQueryDisplayAttribEXT(mDisplay, EGL_DEVICE_EXT, &newEglDevice));
        ASSERT_EGL_TRUE(eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(newEglDevice),
                                                EGL_D3D11_DEVICE_ANGLE, &device));
        angle::ComPtr<ID3D11Device> d3d11Device(reinterpret_cast<ID3D11Device *>(device));
        ASSERT_TRUE(!!d3d11Device);

        constexpr UINT kTextureWidth  = 100;
        constexpr UINT kTextureHeight = 100;
        constexpr Color<uint8_t> kOpaqueBlack(0, 0, 0, 255);
        std::vector<Color<uint8_t>> textureData(kTextureWidth * kTextureHeight, kOpaqueBlack);

        D3D11_SUBRESOURCE_DATA initialData = {};
        initialData.pSysMem                = textureData.data();
        initialData.SysMemPitch            = kTextureWidth * sizeof(kOpaqueBlack);

        D3D11_TEXTURE2D_DESC desc = {};
        desc.Format               = DXGI_FORMAT_B8G8R8A8_UNORM;
        desc.Width                = kTextureWidth;
        desc.Height               = kTextureHeight;
        desc.ArraySize            = 1;
        desc.MipLevels            = 1;
        desc.SampleDesc.Count     = 1;
        desc.Usage                = D3D11_USAGE_DEFAULT;
        desc.BindFlags            = D3D11_BIND_RENDER_TARGET;
        angle::ComPtr<ID3D11Texture2D> texture;
        HRESULT hr = d3d11Device->CreateTexture2D(&desc, &initialData, &texture);
        ASSERT_TRUE(SUCCEEDED(hr));

        angle::ComPtr<ID3D11DeviceContext> d3d11Context;
        d3d11Device->GetImmediateContext(&d3d11Context);

        // Specify a texture offset of (50, 50) when rendering to the pbuffer surface.
        const EGLint surfaceAttributes[] = {EGL_WIDTH,
                                            kTextureWidth,
                                            EGL_HEIGHT,
                                            kTextureHeight,
                                            EGL_TEXTURE_OFFSET_X_ANGLE,
                                            offset,
                                            EGL_TEXTURE_OFFSET_Y_ANGLE,
                                            offset,
                                            EGL_NONE};
        EGLClientBuffer buffer           = reinterpret_cast<EGLClientBuffer>(texture.Get());
        mPbufferSurface = eglCreatePbufferFromClientBuffer(mDisplay, EGL_D3D_TEXTURE_ANGLE, buffer,
                                                           config, surfaceAttributes);
        ASSERT_EGL_SUCCESS();

        eglMakeCurrent(mDisplay, mPbufferSurface, mPbufferSurface, mContext);
        ASSERT_EGL_SUCCESS();

        // glClear should only clear subrect at offset (50, 50) without explicit scissor.
        glClearColor(0, 0, 1, 1);  // Blue
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_PIXEL_EQ(25, 25, 0, 0, pix25, 255);
        EXPECT_PIXEL_EQ(75, 75, 0, 0, pix75, 255);
        EXPECT_GL_NO_ERROR();

        // Drawing with a shader should also update the same subrect only without explicit viewport.
        GLuint program = createProgram();  // Red
        ASSERT_NE(0u, program);
        GLint positionLocation =
            glGetAttribLocation(program, angle::essl1_shaders::PositionAttrib());
        glUseProgram(program);
        const GLfloat vertices[] = {
            -1.0f, 1.0f, 0.5f, -1.0f, -1.0f, 0.5f, 1.0f, -1.0f, 0.5f,
            -1.0f, 1.0f, 0.5f, 1.0f,  -1.0f, 0.5f, 1.0f, 1.0f,  0.5f,
        };
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices);
        glEnableVertexAttribArray(positionLocation);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glDisableVertexAttribArray(positionLocation);
        glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);

        EXPECT_PIXEL_EQ(25, 25, pix25, 0, 0, 255);
        EXPECT_PIXEL_EQ(75, 75, pix75, 0, 0, 255);
        EXPECT_GL_NO_ERROR();

        glDeleteProgram(program);
        EXPECT_GL_NO_ERROR();

        // Blit framebuffer should also blit to the same subrect despite the dstX/Y arguments.
        GLRenderbuffer renderBuffer;
        glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 50, 50);
        EXPECT_GL_NO_ERROR();

        GLFramebuffer framebuffer;
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                                  renderBuffer);
        EXPECT_GL_NO_ERROR();

        glClearColor(0, 1, 0, 1);  // Green
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_PIXEL_EQ(25, 25, 0, 255, 0, 255);
        EXPECT_GL_NO_ERROR();

        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0u);
        glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
        glBlitFramebuffer(0, 0, 50, 50, 0, 0, kTextureWidth, kTextureWidth, GL_COLOR_BUFFER_BIT,
                          GL_NEAREST);
        EXPECT_GL_NO_ERROR();

        glBindFramebuffer(GL_READ_FRAMEBUFFER, 0u);
        EXPECT_PIXEL_EQ(25, 25, 0, pix25, 0, 255);
        EXPECT_PIXEL_EQ(75, 75, 0, pix75, 0, 255);
        EXPECT_GL_NO_ERROR();
    }

    // Draws into a surface at the specified offset using the values of gl_FragCoord in the
    // fragment shader.
    // texturedimension - dimension of the D3D texture and surface.
    // offset - draw into the texture at offset (|offset|, |offset|)
    void setupFragCoordOffset(int textureDimension, int offset)
    {
        ANGLE_SKIP_TEST_IF(!IsEGLClientExtensionEnabled("EGL_ANGLE_platform_angle_d3d"));
        initializeDisplay();

        EGLAttrib device       = 0;
        EGLAttrib newEglDevice = 0;
        ASSERT_EGL_TRUE(eglQueryDisplayAttribEXT(mDisplay, EGL_DEVICE_EXT, &newEglDevice));
        ASSERT_EGL_TRUE(eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(newEglDevice),
                                                EGL_D3D11_DEVICE_ANGLE, &device));
        angle::ComPtr<ID3D11Device> d3d11Device(reinterpret_cast<ID3D11Device *>(device));
        ASSERT_TRUE(!!d3d11Device);

        D3D11_TEXTURE2D_DESC desc = {};
        desc.Format               = DXGI_FORMAT_B8G8R8A8_UNORM;
        desc.Width                = textureDimension;
        desc.Height               = textureDimension;
        desc.ArraySize            = 1;
        desc.MipLevels            = 1;
        desc.SampleDesc.Count     = 1;
        desc.Usage                = D3D11_USAGE_DEFAULT;
        desc.BindFlags            = D3D11_BIND_RENDER_TARGET;
        angle::ComPtr<ID3D11Texture2D> texture;
        HRESULT hr = d3d11Device->CreateTexture2D(&desc, nullptr, &texture);
        ASSERT_TRUE(SUCCEEDED(hr));

        const EGLint surfaceAttributes[] = {EGL_WIDTH,
                                            textureDimension,
                                            EGL_HEIGHT,
                                            textureDimension,
                                            EGL_TEXTURE_OFFSET_X_ANGLE,
                                            offset,
                                            EGL_TEXTURE_OFFSET_Y_ANGLE,
                                            offset,
                                            EGL_NONE};
        EGLClientBuffer buffer           = reinterpret_cast<EGLClientBuffer>(texture.Get());

        const EGLint configAttributes[] = {
            EGL_RED_SIZE,   8, EGL_GREEN_SIZE,   8, EGL_BLUE_SIZE,      8, EGL_ALPHA_SIZE, 8,
            EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, 0, EGL_SAMPLE_BUFFERS, 0, EGL_NONE};

        EGLConfig config;
        ASSERT_EGL_TRUE(EGLWindow::FindEGLConfig(mDisplay, configAttributes, &config));
        mConfig = config;

        mPbufferSurface = eglCreatePbufferFromClientBuffer(mDisplay, EGL_D3D_TEXTURE_ANGLE, buffer,
                                                           config, surfaceAttributes);
        ASSERT_EGL_SUCCESS();

        initializeMainContext();

        eglMakeCurrent(mDisplay, mPbufferSurface, mPbufferSurface, mContext);
        ASSERT_EGL_SUCCESS();

        // Fragment shader that uses the gl_FragCoord values to output the (x, y) position of
        // the current pixel as the color.
        //    - Reverse the offset that was applied to the original coordinates
        //    - 0.5 is subtracted because gl_FragCoord gives the pixel center
        //    - Divided by the size to give a max value of 1
        std::stringstream fs;
        fs << "precision mediump float;" << "void main()" << "{" << "    float dimension = float("
           << textureDimension << ");" << "    float offset = float(" << offset << ");"
           << "    gl_FragColor = vec4((gl_FragCoord.x + offset - 0.5) / dimension,"
           << "                        (gl_FragCoord.y + offset - 0.5) / dimension,"
           << "                         gl_FragCoord.z,"
           << "                         gl_FragCoord.w);" << "}";

        GLuint program = createProgram(fs.str().c_str());
        ASSERT_NE(0u, program);
        glUseProgram(program);

        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);

        const GLfloat vertices[] = {
            -1.0f, 1.0f, 0.5f, -1.0f, -1.0f, 0.5f, 1.0f, -1.0f, 0.5f,
            -1.0f, 1.0f, 0.5f, 1.0f,  -1.0f, 0.5f, 1.0f, 1.0f,  0.5f,
        };

        GLint positionLocation =
            glGetAttribLocation(program, angle::essl1_shaders::PositionAttrib());
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices);
        glEnableVertexAttribArray(positionLocation);

        glDrawArrays(GL_TRIANGLES, 0, 6);

        glDisableVertexAttribArray(positionLocation);

        glDeleteProgram(program);

        EXPECT_GL_NO_ERROR();
    }
};

// Test that rendering to an IDCompositionSurface using a pbuffer works.
TEST_P(EGLSurfaceTestD3D11, CreateDirectCompositionSurface)
{
    ANGLE_SKIP_TEST_IF(!IsEGLClientExtensionEnabled("EGL_ANGLE_platform_angle_d3d"));
    initializeDisplay();

    EGLAttrib device       = 0;
    EGLAttrib newEglDevice = 0;
    ASSERT_EGL_TRUE(eglQueryDisplayAttribEXT(mDisplay, EGL_DEVICE_EXT, &newEglDevice));
    ASSERT_EGL_TRUE(eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(newEglDevice),
                                            EGL_D3D11_DEVICE_ANGLE, &device));
    angle::ComPtr<ID3D11Device> d3d11Device(reinterpret_cast<ID3D11Device *>(device));
    ASSERT_TRUE(!!d3d11Device);

    HMODULE dcompLibrary = LoadLibraryA("dcomp.dll");
    if (!dcompLibrary)
    {
        std::cout << "DirectComposition not supported" << std::endl;
        return;
    }
    typedef HRESULT(WINAPI * PFN_DCOMPOSITION_CREATE_DEVICE2)(IUnknown * dxgiDevice, REFIID iid,
                                                              void **dcompositionDevice);
    PFN_DCOMPOSITION_CREATE_DEVICE2 createDComp = reinterpret_cast<PFN_DCOMPOSITION_CREATE_DEVICE2>(
        GetProcAddress(dcompLibrary, "DCompositionCreateDevice2"));
    if (!createDComp)
    {
        std::cout << "DirectComposition2 not supported" << std::endl;
        FreeLibrary(dcompLibrary);
        return;
    }

    angle::ComPtr<IDCompositionDevice> dcompDevice;
    HRESULT hr = createDComp(d3d11Device.Get(), IID_PPV_ARGS(dcompDevice.GetAddressOf()));
    ASSERT_TRUE(SUCCEEDED(hr));

    angle::ComPtr<IDCompositionSurface> dcompSurface;
    hr = dcompDevice->CreateSurface(100, 100, DXGI_FORMAT_B8G8R8A8_UNORM,
                                    DXGI_ALPHA_MODE_PREMULTIPLIED, dcompSurface.GetAddressOf());
    ASSERT_TRUE(SUCCEEDED(hr));

    angle::ComPtr<ID3D11Texture2D> texture;
    POINT updateOffset;
    hr = dcompSurface->BeginDraw(nullptr, IID_PPV_ARGS(texture.GetAddressOf()), &updateOffset);
    ASSERT_TRUE(SUCCEEDED(hr));

    const EGLint configAttributes[] = {
        EGL_RED_SIZE,   8, EGL_GREEN_SIZE,   8, EGL_BLUE_SIZE,      8, EGL_ALPHA_SIZE, 8,
        EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, 0, EGL_SAMPLE_BUFFERS, 0, EGL_NONE};

    EGLConfig config;
    ASSERT_EGL_TRUE(EGLWindow::FindEGLConfig(mDisplay, configAttributes, &config));

    const EGLint surfaceAttributes[] = {EGL_WIDTH,
                                        100,
                                        EGL_HEIGHT,
                                        100,
                                        EGL_TEXTURE_OFFSET_X_ANGLE,
                                        updateOffset.x,
                                        EGL_TEXTURE_OFFSET_Y_ANGLE,
                                        updateOffset.y,
                                        EGL_NONE};

    EGLClientBuffer buffer = reinterpret_cast<EGLClientBuffer>(texture.Get());
    mPbufferSurface = eglCreatePbufferFromClientBuffer(mDisplay, EGL_D3D_TEXTURE_ANGLE, buffer,
                                                       config, surfaceAttributes);
    ASSERT_EGL_SUCCESS();

    mConfig = config;
    initializeMainContext();

    eglMakeCurrent(mDisplay, mPbufferSurface, mPbufferSurface, mContext);
    ASSERT_EGL_SUCCESS();

    GLuint program = createProgram();
    ASSERT_NE(0u, program);
    drawWithProgram(program);
    EXPECT_GL_NO_ERROR();
    glDeleteProgram(program);
}

// Tests drawing into a surface created with negative offsets.
TEST_P(EGLSurfaceTestD3D11, CreateSurfaceWithTextureNegativeOffset)
{
    ANGLE_SKIP_TEST_IF(!IsEGLClientExtensionEnabled("EGL_ANGLE_platform_angle_d3d"));
    testTextureOffset(-50, 255, 0);
}

// Tests drawing into a surface created with offsets.
TEST_P(EGLSurfaceTestD3D11, CreateSurfaceWithTextureOffset)
{
    ANGLE_SKIP_TEST_IF(!IsEGLClientExtensionEnabled("EGL_ANGLE_platform_angle_d3d"));
    testTextureOffset(50, 0, 255);
}

TEST_P(EGLSurfaceTestD3D11, CreateSurfaceWithMSAA)
{
    ANGLE_SKIP_TEST_IF(!IsEGLClientExtensionEnabled("EGL_ANGLE_platform_angle_d3d"));

    // clang-format off
    const EGLint configAttributes[] =
    {
        EGL_RED_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE, 8,
        EGL_ALPHA_SIZE, 0,
        EGL_DEPTH_SIZE, 0,
        EGL_STENCIL_SIZE, 0,
        EGL_SAMPLE_BUFFERS, 1,
        EGL_SAMPLES, 4,
        EGL_NONE
    };
    // clang-format on

    initializeDisplay();
    EGLConfig config;
    if (EGLWindow::FindEGLConfig(mDisplay, configAttributes, &config) == EGL_FALSE)
    {
        std::cout << "EGLConfig for 4xMSAA is not supported, skipping test" << std::endl;
        return;
    }

    initializeSurface(config);
    initializeMainContext();

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    GLuint program = createProgram();
    ASSERT_NE(0u, program);

    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    GLint positionLocation = glGetAttribLocation(program, angle::essl1_shaders::PositionAttrib());
    ASSERT_NE(-1, positionLocation);

    glUseProgram(program);

    const GLfloat halfPixelOffset = 0.5f * 2.0f / mOSWindow->getWidth();
    // clang-format off
    const GLfloat vertices[] =
    {
        -1.0f + halfPixelOffset,  1.0f, 0.5f,
        -1.0f + halfPixelOffset, -1.0f, 0.5f,
         1.0f,                   -1.0f, 0.5f
    };
    // clang-format on

    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(positionLocation);

    glDrawArrays(GL_TRIANGLES, 0, 3);

    glDisableVertexAttribArray(positionLocation);
    glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);

    EXPECT_PIXEL_NEAR(0, 0, 127, 0, 0, 255, 10);
    EXPECT_GL_NO_ERROR();

    glDeleteProgram(program);
}

// Tests that gl_FragCoord.xy is offset with the EGL_TEXTURE_OFFSET_[X|Y]_ANGLE values specified
// at surface creation, using positive offsets
TEST_P(EGLSurfaceTestD3D11, FragCoordOffset)
{
    constexpr int kTextureDimension = 28;
    constexpr int kOffset           = 6;

    setupFragCoordOffset(kTextureDimension, kOffset);

    // With a positive offset, nothing is drawn in any pixels to the left of and above |kOffset|.
    for (int x = 0; x < kOffset; x++)
    {
        for (int y = 0; y < kOffset; y++)
        {
            EXPECT_PIXEL_EQ(x, y, 0, 0, 0, 0);
        }
    }

    // The rest of the texture's color should be the value of the (x, y) coordinates.
    for (int x = kOffset; x < kTextureDimension; x++)
    {
        for (int y = kOffset; y < kTextureDimension; y++)
        {
            EXPECT_PIXEL_NEAR(x, y, x * 255.0 / kTextureDimension, y * 255.0 / kTextureDimension,
                              191, 255, 0.5);
        }
    }
}

// Tests that gl_FragCoord.xy is offset with the EGL_TEXTURE_OFFSET_[X|Y]_ANGLE values specified
// at surface creation, using negative offsets.
TEST_P(EGLSurfaceTestD3D11, FragCoordOffsetNegative)
{
    constexpr int kTextureDimension = 28;
    constexpr int kOffset           = 6;

    setupFragCoordOffset(kTextureDimension, -kOffset);

    // With a negative offset, nothing is drawn in pixels to the right of and below |koffset|.
    for (int x = kTextureDimension - kOffset; x < kTextureDimension; x++)
    {
        for (int y = kTextureDimension - kOffset; y < kTextureDimension; y++)
        {
            EXPECT_PIXEL_EQ(x, y, 0, 0, 0, 0);
        }
    }

    // The rest of the texture's color should be the value of the (x, y) coordinates.
    for (int x = 0; x < kTextureDimension - kOffset; x++)
    {
        for (int y = 0; y < kTextureDimension - kOffset; y++)
        {
            EXPECT_PIXEL_NEAR(x, y, x * 255.0 / kTextureDimension, y * 255.0 / kTextureDimension,
                              191, 255, 0.5);
        }
    }
}

#endif  // ANGLE_ENABLE_D3D11

// Verify bliting between two surfaces works correctly.
TEST_P(EGLSurfaceTest3, BlitBetweenSurfaces)
{
    initializeDisplay();
    ASSERT_NE(mDisplay, EGL_NO_DISPLAY);

    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);
    ASSERT_NE(mContext, EGL_NO_CONTEXT);

    EGLSurface surface1;
    EGLSurface surface2;

    const EGLint surfaceAttributes[] = {
        EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE,
    };

    surface1 = eglCreatePbufferSurface(mDisplay, mConfig, surfaceAttributes);
    ASSERT_EGL_SUCCESS();
    surface2 = eglCreatePbufferSurface(mDisplay, mConfig, surfaceAttributes);
    ASSERT_EGL_SUCCESS();

    // Clear surface1.
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface1, surface1, mContext));

    // TODO(http://anglebug.com/42264803): Failing with OpenGL ES backend on Android and
    // Windows. Must be after the eglMakeCurrent() so the renderer string is initialized.
    ANGLE_SKIP_TEST_IF(IsOpenGLES() && (IsAndroid() || IsWindows()));

    glClearColor(kFloatRed.R, kFloatRed.G, kFloatRed.B, kFloatRed.A);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();

    // Blit from surface1 to surface2.
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface2, surface1, mContext));
    glBlitFramebuffer(0, 0, 64, 64, 0, 0, 64, 64, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Confirm surface1 has the clear color.
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface1, surface1, mContext));
    EXPECT_PIXEL_COLOR_EQ(32, 32, GLColor::red);

    // Confirm surface2 has the blited clear color.
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface2, surface2, mContext));
    EXPECT_PIXEL_COLOR_EQ(32, 32, GLColor::red);

    eglDestroySurface(mDisplay, surface1);
    eglDestroySurface(mDisplay, surface2);
}

// Verify bliting between two surfaces works correctly.
TEST_P(EGLSurfaceTest3, BlitBetweenSurfacesWithDeferredClear)
{
    initializeDisplay();
    ASSERT_NE(mDisplay, EGL_NO_DISPLAY);

    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);
    ASSERT_NE(mContext, EGL_NO_CONTEXT);

    EGLSurface surface1;
    EGLSurface surface2;

    const EGLint surfaceAttributes[] = {
        EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE,
    };

    surface1 = eglCreatePbufferSurface(mDisplay, mConfig, surfaceAttributes);
    ASSERT_EGL_SUCCESS();
    surface2 = eglCreatePbufferSurface(mDisplay, mConfig, surfaceAttributes);
    ASSERT_EGL_SUCCESS();

    // Clear surface1.
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface1, surface1, mContext));

    // TODO(http://anglebug.com/42264803): Failing with OpenGL ES backend on Android and
    // Windows. Must be after the eglMakeCurrent() so the renderer string is initialized.
    ANGLE_SKIP_TEST_IF(IsOpenGLES() && (IsAndroid() || IsWindows()));

    glClearColor(kFloatRed.R, kFloatRed.G, kFloatRed.B, kFloatRed.A);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();
    // Force the clear to be flushed
    EXPECT_PIXEL_COLOR_EQ(32, 32, GLColor::red);

    // Clear to green, but don't read it back so the clear is deferred.
    glClearColor(kFloatGreen.R, kFloatGreen.G, kFloatGreen.B, kFloatGreen.A);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();

    // Blit from surface1 to surface2.
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface2, surface1, mContext));
    glBlitFramebuffer(0, 0, 64, 64, 0, 0, 64, 64, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Confirm surface1 has the clear color.
    EXPECT_PIXEL_COLOR_EQ(32, 32, GLColor::green);

    // Confirm surface2 has the blited clear color.
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface2, surface2, mContext));
    EXPECT_PIXEL_COLOR_EQ(32, 32, GLColor::green);

    eglDestroySurface(mDisplay, surface1);
    eglDestroySurface(mDisplay, surface2);
}

// Verify switching between a surface with robust resource init and one without still clears alpha.
TEST_P(EGLSurfaceTest, RobustResourceInitAndEmulatedAlpha)
{
    // http://anglebug.com/42263827
    ANGLE_SKIP_TEST_IF(IsNVIDIA() && isGLRenderer() && IsLinux());

    // http://anglebug.com/40644775
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsNexus5X() && isGLESRenderer());

    initializeDisplay();
    ASSERT_NE(mDisplay, EGL_NO_DISPLAY);

    ANGLE_SKIP_TEST_IF(
        !IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANGLE_robust_resource_initialization"));

    // Initialize and draw red to a Surface with robust resource init enabled.
    constexpr EGLint kRGBAAttributes[] = {EGL_RED_SIZE,     8,
                                          EGL_GREEN_SIZE,   8,
                                          EGL_BLUE_SIZE,    8,
                                          EGL_ALPHA_SIZE,   8,
                                          EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                                          EGL_NONE};

    EGLint configCount   = 0;
    EGLConfig rgbaConfig = nullptr;
    ASSERT_EGL_TRUE(eglChooseConfig(mDisplay, kRGBAAttributes, &rgbaConfig, 1, &configCount));
    ASSERT_EQ(configCount, 1);
    ASSERT_NE(rgbaConfig, nullptr);

    std::vector<EGLint> robustInitAttribs;
    robustInitAttribs.push_back(EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE);
    robustInitAttribs.push_back(EGL_TRUE);

    initializeSurfaceWithAttribs(rgbaConfig, robustInitAttribs);
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    initializeMainContext();
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(mContext, EGL_NO_CONTEXT);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();
    eglSwapBuffers(mDisplay, mWindowSurface);

    // RGBA robust init setup complete. Draw red and verify.
    {
        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        glUseProgram(program);

        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

        eglSwapBuffers(mDisplay, mWindowSurface);
    }

    tearDownContextAndSurface();

    // Create second RGB surface with robust resource disabled.
    constexpr EGLint kRGBAttributes[] = {EGL_RED_SIZE,     8,
                                         EGL_GREEN_SIZE,   8,
                                         EGL_BLUE_SIZE,    8,
                                         EGL_ALPHA_SIZE,   0,
                                         EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                                         EGL_NONE};

    configCount         = 0;
    EGLConfig rgbConfig = nullptr;
    ASSERT_EGL_TRUE(eglChooseConfig(mDisplay, kRGBAttributes, &rgbConfig, 1, &configCount));
    ASSERT_EQ(configCount, 1);
    ASSERT_NE(rgbConfig, nullptr);

    initializeSurface(rgbConfig);
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    initializeMainContext();

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // RGB non-robust init setup complete. Draw red and verify.
    {
        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        glUseProgram(program);

        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

        eglSwapBuffers(mDisplay, mWindowSurface);
    }
}

void EGLSurfaceTest::drawQuadThenTearDown()
{
    initializeMainContext();

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    {
        ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
        drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();
    }

    tearDownContextAndSurface();
}

// Tests the EGL_ANGLE_create_surface_swap_interval extension if available.
TEST_P(EGLSurfaceTest, CreateSurfaceSwapIntervalANGLE)
{
    initializeDisplay();
    ASSERT_NE(mDisplay, EGL_NO_DISPLAY);

    mConfig = chooseDefaultConfig(true);
    ASSERT_NE(mConfig, nullptr);

    if (IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANGLE_create_surface_swap_interval"))
    {
        // Test error conditions.
        EGLint minSwapInterval = 0;
        eglGetConfigAttrib(mDisplay, mConfig, EGL_MIN_SWAP_INTERVAL, &minSwapInterval);
        ASSERT_EGL_SUCCESS();

        if (minSwapInterval > 0)
        {
            std::vector<EGLint> min1SwapAttribs = {EGL_SWAP_INTERVAL_ANGLE, minSwapInterval - 1};
            initializeWindowSurfaceWithAttribs(mConfig, min1SwapAttribs, EGL_BAD_ATTRIBUTE);
        }

        EGLint maxSwapInterval = 0;
        eglGetConfigAttrib(mDisplay, mConfig, EGL_MAX_SWAP_INTERVAL, &maxSwapInterval);
        ASSERT_EGL_SUCCESS();

        if (maxSwapInterval < std::numeric_limits<EGLint>::max())
        {
            std::vector<EGLint> max1SwapAttribs = {EGL_SWAP_INTERVAL_ANGLE, maxSwapInterval + 1};
            initializeWindowSurfaceWithAttribs(mConfig, max1SwapAttribs, EGL_BAD_ATTRIBUTE);
        }

        // Test valid min/max usage.
        {
            std::vector<EGLint> minSwapAttribs = {EGL_SWAP_INTERVAL_ANGLE, minSwapInterval};
            initializeWindowSurfaceWithAttribs(mConfig, minSwapAttribs, EGL_SUCCESS);
            drawQuadThenTearDown();
        }

        if (minSwapInterval != maxSwapInterval)
        {
            std::vector<EGLint> maxSwapAttribs = {EGL_SWAP_INTERVAL_ANGLE, maxSwapInterval};
            initializeWindowSurfaceWithAttribs(mConfig, maxSwapAttribs, EGL_SUCCESS);
            drawQuadThenTearDown();
        }
    }
    else
    {
        // Test extension unavailable error.
        std::vector<EGLint> swapInterval1Attribs = {EGL_SWAP_INTERVAL_ANGLE, 1};
        initializeWindowSurfaceWithAttribs(mConfig, swapInterval1Attribs, EGL_BAD_ATTRIBUTE);
    }
}

// Test that setting a surface's timestamp attribute works when the extension
// EGL_ANGLE_timestamp_surface_attribute is supported.
TEST_P(EGLSurfaceTest, TimestampSurfaceAttribute)
{
    initializeDisplay();
    ASSERT_NE(mDisplay, EGL_NO_DISPLAY);
    mConfig = chooseDefaultConfig(true);
    ASSERT_NE(mConfig, nullptr);
    initializeSurface(mConfig);
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);
    initializeMainContext();

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    const bool extensionSupported =
        IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANDROID_get_frame_timestamps") ||
        IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANGLE_timestamp_surface_attribute");

    EGLBoolean setSurfaceAttrib =
        eglSurfaceAttrib(mDisplay, mWindowSurface, EGL_TIMESTAMPS_ANDROID, EGL_TRUE);

    if (extensionSupported)
    {
        EXPECT_EGL_TRUE(setSurfaceAttrib);

        // Swap so the swapchain gets created.
        glClearColor(1.0, 1.0, 1.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_EGL_TRUE(eglSwapBuffers(mDisplay, mWindowSurface));

        // Query to confirm the attribute persists across swaps.
        EGLint timestampEnabled = 0;
        EXPECT_EGL_TRUE(
            eglQuerySurface(mDisplay, mWindowSurface, EGL_TIMESTAMPS_ANDROID, &timestampEnabled));
        EXPECT_NE(timestampEnabled, 0);

        // Resize window and swap.
        mOSWindow->resize(256, 256);
        glClearColor(1.0, 1.0, 1.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_EGL_TRUE(eglSwapBuffers(mDisplay, mWindowSurface));

        // Query to confirm the attribute persists across swapchain recreations.
        timestampEnabled = 0;
        EXPECT_EGL_TRUE(
            eglQuerySurface(mDisplay, mWindowSurface, EGL_TIMESTAMPS_ANDROID, &timestampEnabled));
        EXPECT_NE(timestampEnabled, 0);
    }
    else
    {
        EXPECT_EGL_FALSE(setSurfaceAttrib);
        EXPECT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";
}

TEST_P(EGLSingleBufferTest, OnCreateWindowSurface)
{
    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfig(&config, true));

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_SINGLE_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    bool skipped = false;
    EGLint actualRenderbuffer;
    EXPECT_EGL_TRUE(eglQueryContext(mDisplay, context, EGL_RENDER_BUFFER, &actualRenderbuffer));
    if (actualRenderbuffer == EGL_SINGLE_BUFFER)
    {
        EXPECT_EGL_TRUE(actualRenderbuffer == EGL_SINGLE_BUFFER);

        glEnable(GL_SCISSOR_TEST);
        glScissor(0, 0, 2, 2);

        glClearColor(0.0, 1.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();
        ASSERT_GL_NO_ERROR();
        // Flush should result in update of screen. Must be visually confirmed.
        // Pixel test for automation.
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::green);

        // Check second flush
        glClearColor(1.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::red);
    }
    else
    {
        std::cout << "SKIP test, no EGL_SINGLE_BUFFER support." << std::endl;
        skipped = true;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
    ANGLE_SKIP_TEST_IF(skipped);
}

TEST_P(EGLSingleBufferTest, OnSetSurfaceAttrib)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfig(&config, true));

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));

    // Transition into EGL_SINGLE_BUFFER mode.
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    if (eglSwapBuffers(mDisplay, surface))
    {
        EGLint actualRenderbuffer;
        EXPECT_EGL_TRUE(eglQueryContext(mDisplay, context, EGL_RENDER_BUFFER, &actualRenderbuffer));
        EXPECT_EGL_TRUE(actualRenderbuffer == EGL_SINGLE_BUFFER);

        glEnable(GL_SCISSOR_TEST);
        glScissor(0, 0, 2, 2);

        glClearColor(0.0, 1.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();
        // Flush should result in update of screen. Must be visually confirmed Green window.

        // Check color for automation.
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::green);

        // Check second flush
        glClearColor(1.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::red);

        // Switch back to EGL_BACK_BUFFER and check.
        EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_BACK_BUFFER));
        glClearColor(1.0, 1.0, 1.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_EGL_TRUE(eglSwapBuffers(mDisplay, surface));

        EXPECT_EGL_TRUE(eglQueryContext(mDisplay, context, EGL_RENDER_BUFFER, &actualRenderbuffer));
        EXPECT_EGL_TRUE(actualRenderbuffer == EGL_BACK_BUFFER);

        glClearColor(1.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::red);
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

uint32_t EGLSingleBufferTest::drawAndSwap(EGLSurface &surface,
                                          EGLDisplay &display,
                                          uint32_t color,
                                          bool flush)
{
    ASSERT(color < 256);

    glClearColor((float)color / 255.f, (float)color / 255.f, (float)color / 255.f,
                 (float)color / 255.f);
    glClear(GL_COLOR_BUFFER_BIT);

    if (flush)
    {
        glFlush();
    }
    else
    {
        eglSwapBuffers(display, surface);
    }

    return (color | color << 8 | color << 16 | color << 24);
}

// Replicate dEQP-EGL.functional.mutable_render_buffer#basic
TEST_P(EGLSingleBufferTest, MutableRenderBuffer)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config       = EGL_NO_CONFIG_KHR;
    const EGLint attribs[] = {EGL_RED_SIZE,
                              8,
                              EGL_GREEN_SIZE,
                              8,
                              EGL_BLUE_SIZE,
                              8,
                              EGL_ALPHA_SIZE,
                              8,
                              EGL_SURFACE_TYPE,
                              EGL_WINDOW_BIT | EGL_MUTABLE_RENDER_BUFFER_BIT_KHR,
                              EGL_RENDERABLE_TYPE,
                              EGL_OPENGL_ES2_BIT,
                              EGL_NONE};
    EGLint count           = 0;
    ANGLE_SKIP_TEST_IF(!eglChooseConfig(mDisplay, attribs, &config, 1, &count));
    ANGLE_SKIP_TEST_IF(count == 0);

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    int frameNumber = 1;

    // run a few back-buffered frames
    for (; frameNumber < 5; frameNumber++)
    {
        drawAndSwap(surface, mDisplay, frameNumber, false);
    }

    if (eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER))
    {
        drawAndSwap(surface, mDisplay, frameNumber, false);
        frameNumber++;

        // test a few single-buffered frames
        for (; frameNumber < 10; frameNumber++)
        {
            uint32_t backBufferPixel  = 0xFFFFFFFF;
            uint32_t frontBufferPixel = drawAndSwap(surface, mDisplay, frameNumber, true);
            glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &backBufferPixel);
            EXPECT_EQ(backBufferPixel, frontBufferPixel);
        }
    }

    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    // switch back to back-buffer rendering
    if (eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_BACK_BUFFER))
    {
        for (; frameNumber < 14; frameNumber++)
        {
            drawAndSwap(surface, mDisplay, frameNumber, false);
        }
    }
    else
    {
        std::cout << "EGL_BACK_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Tests bug with incorrect ImageLayout::SharedPresent barrier.
TEST_P(EGLSingleBufferTest, SharedPresentBarrier)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfig(&config, true));

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));

    // Transition into EGL_SINGLE_BUFFER mode.
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    if (eglSwapBuffers(mDisplay, surface))
    {
        EGLint actualRenderbuffer;
        EXPECT_EGL_TRUE(eglQueryContext(mDisplay, context, EGL_RENDER_BUFFER, &actualRenderbuffer));
        EXPECT_EGL_TRUE(actualRenderbuffer == EGL_SINGLE_BUFFER);

        for (int i = 0; i < 5; ++i)
        {
            GLColor testColor(rand() % 256, rand() % 256, rand() % 256, 255);
            angle::Vector4 clearColor = testColor.toNormalizedVector();
            glClearColor(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
            glClear(GL_COLOR_BUFFER_BIT);
            // Skip flush because present operations may add other barriers that will make appear
            // that everything works as expected.

            // Check color without flush - may get invalid result if have incorrect barrier bug.
            EXPECT_PIXEL_COLOR_EQ(1, 1, testColor);
        }
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Tests scissored clear on single buffer surface
TEST_P(EGLSingleBufferTest, ScissoredClear)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfig(&config, true));

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));
    if (eglSwapBuffers(mDisplay, surface))
    {
        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();

        glEnable(GL_SCISSOR_TEST);
        glScissor(1, 1, 10, 10);
        glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(2, 2, GLColor::green);
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Tests scissored draw on single buffer surface
TEST_P(EGLSingleBufferTest, ScissoredDraw)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfig(&config, true));

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));
    if (eglSwapBuffers(mDisplay, surface))
    {
        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();

        glEnable(GL_SCISSOR_TEST);
        glScissor(1, 1, 10, 10);
        glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
        ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
        drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f);
        glFlush();
        glDisable(GL_SCISSOR_TEST);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
        EXPECT_PIXEL_COLOR_EQ(2, 2, GLColor::green);
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Tests that "one off" submission is waited before destroying the surface.
TEST_P(EGLSingleBufferTest, WaitOneOffSubmission)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfig(&config, true));

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLContext context2 = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context2));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    const EGLint pbufferSurfaceAttrs[] = {
        EGL_WIDTH, 1024, EGL_HEIGHT, 1024, EGL_NONE,
    };
    EGLSurface pbufferSurface = eglCreatePbufferSurface(mDisplay, config, pbufferSurfaceAttrs);
    ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    // Query age for the first time to avoid submitting debug information a second time.
    EGLint age = 0;
    EXPECT_EGL_TRUE(eglQuerySurface(mDisplay, surface, EGL_BUFFER_AGE_EXT, &age));

    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));
    // Transition into EGL_SINGLE_BUFFER mode.
    if (eglSwapBuffers(mDisplay, surface))
    {
        // Submit heavy work to the GPU before querying the buffer age.
        std::thread([this, context2, pbufferSurface]() {
            EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, pbufferSurface, pbufferSurface, context2));
            ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

            ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
            drawQuadInstanced(greenProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, false,
                              1000);

            EXPECT_EGL_TRUE(
                eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
            ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";
        }).join();

        // Querying the buffer age should perform first acquire of the image and "one off"
        // submission to change image layout to the VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR.
        EXPECT_EGL_TRUE(eglQuerySurface(mDisplay, surface, EGL_BUFFER_AGE_EXT, &age));
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroySurface(mDisplay, pbufferSurface);
    pbufferSurface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;

    eglDestroyContext(mDisplay, context2);
    context2 = EGL_NO_CONTEXT;
}

// Checks that |WindowSurfaceVk::swamImpl| acquires and process next swapchain image in case of
// shared present mode, when called from flush.
TEST_P(EGLSingleBufferTest, AcquireImageFromSwapImpl)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfig(&config, true));

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));
    if (eglSwapBuffers(mDisplay, surface))
    {
        ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

        // Draw into the single buffered surface.
        // Acquire next swapchain image should be deferred (Vulkan back-end).
        drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f);
        glFlush();

        // Prepare auxiliary framebuffer.
        GLRenderbuffer renderBuffer;
        GLFramebuffer framebuffer;
        glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 50, 50);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                                  renderBuffer);
        EXPECT_GL_NO_ERROR();

        // Draw into the auxiliary framebuffer just to generate commands into the command buffers.
        // Otherwise below flush will be ignored.
        drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f);

        // Switch back to the Windows Surface and perform flush.
        // In Vulkan back-end flush will translate into |swapImpl| call while acquire next swapchain
        // image is still deferred. |swapImpl| must perform the acquire in that case, otherwise
        // ASSERT will trigger in |present|.
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glFlush();
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Tests that staged clear into MSAA surface is resolved and not dropped on swap.
TEST_P(EGLSingleBufferTest, StagedClearResolveOnSwap)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config       = EGL_NO_CONFIG_KHR;
    const EGLint attribs[] = {EGL_RED_SIZE,
                              8,
                              EGL_GREEN_SIZE,
                              8,
                              EGL_BLUE_SIZE,
                              8,
                              EGL_ALPHA_SIZE,
                              8,
                              EGL_SAMPLE_BUFFERS,
                              1,
                              EGL_SAMPLES,
                              4,
                              EGL_SURFACE_TYPE,
                              EGL_WINDOW_BIT | EGL_MUTABLE_RENDER_BUFFER_BIT_KHR,
                              EGL_RENDERABLE_TYPE,
                              EGL_OPENGL_ES2_BIT,
                              EGL_NONE};
    EGLint count           = 0;
    ANGLE_SKIP_TEST_IF(!eglChooseConfig(mDisplay, attribs, &config, 1, &count));
    ANGLE_SKIP_TEST_IF(count == 0);

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));
    if (eglSwapBuffers(mDisplay, surface))
    {
        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // Flush here performs swap and tests the code that performs the resolve optimization.
        glFlush();

        // Confirm that staged clear was not dropped in the above resolve on swap optimization.
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Tests that SharedPresent image layout is not changed by the renderpass resolve operation.
TEST_P(EGLSingleBufferTest, SharedPresentLayoutWithMSAA)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config       = EGL_NO_CONFIG_KHR;
    const EGLint attribs[] = {EGL_RED_SIZE,
                              8,
                              EGL_GREEN_SIZE,
                              8,
                              EGL_BLUE_SIZE,
                              8,
                              EGL_ALPHA_SIZE,
                              8,
                              EGL_SAMPLE_BUFFERS,
                              1,
                              EGL_SAMPLES,
                              4,
                              EGL_SURFACE_TYPE,
                              EGL_WINDOW_BIT | EGL_MUTABLE_RENDER_BUFFER_BIT_KHR,
                              EGL_RENDERABLE_TYPE,
                              EGL_OPENGL_ES2_BIT,
                              EGL_NONE};
    EGLint count           = 0;
    ANGLE_SKIP_TEST_IF(!eglChooseConfig(mDisplay, attribs, &config, 1, &count));
    ANGLE_SKIP_TEST_IF(count == 0);

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));
    if (eglSwapBuffers(mDisplay, surface))
    {
        // Draw using renderpass.
        ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
        drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f);
        // This should perform resolve with renderpass but must keep the SharedPresent layout.
        glFlush();
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Tests that Window resize does not invalidate the previous content.
TEST_P(EGLSingleBufferTest, WindowResize)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfig(&config, true));

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));

    // Transition into EGL_SINGLE_BUFFER mode.
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    if (eglSwapBuffers(mDisplay, surface))
    {
        EGLint actualRenderbuffer;
        EXPECT_EGL_TRUE(eglQueryContext(mDisplay, context, EGL_RENDER_BUFFER, &actualRenderbuffer));
        EXPECT_EGL_TRUE(actualRenderbuffer == EGL_SINGLE_BUFFER);

        glEnable(GL_SCISSOR_TEST);

        // Draw before resize
        glScissor(0, 0, 2, 2);
        glClearColor(0.0, 1.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();

        // Window resize should not invalidate the previous content.  Note, window resize may be
        // ignored to preserve the content (native GLES Android behavior).
        mOSWindow->resize(kWidth + 16, kHeight + 16);

        // Draw after resize
        glScissor(1, 1, 1, 1);
        glClearColor(1.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();

        // Check result from both draws.
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::red);
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Tests that Window rotation does not invalidate the previous content.
TEST_P(EGLSingleBufferTest, WindowRotation)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));
    // This test uses functionality that is only available on Android
    ANGLE_SKIP_TEST_IF(!IsAndroid());

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfig(&config, true));

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));

    // Transition into EGL_SINGLE_BUFFER mode.
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    if (eglSwapBuffers(mDisplay, surface))
    {
        EGLint actualRenderbuffer;
        EXPECT_EGL_TRUE(eglQueryContext(mDisplay, context, EGL_RENDER_BUFFER, &actualRenderbuffer));
        EXPECT_EGL_TRUE(actualRenderbuffer == EGL_SINGLE_BUFFER);

        glEnable(GL_SCISSOR_TEST);

        // Set landscape orientation.  Note, this will not change window size.
        mOSWindow->setOrientation(200, 100);
        angle::Sleep(1000);

        // Draw in landscape orientation.
        glScissor(0, 0, 2, 2);
        glClearColor(0.0, 1.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();

        // Set portrait orientation.  Window rotation should not invalidate the previous content.
        // Note, window rotation may be ignored to preserve content (native GLES Android behavior).
        mOSWindow->setOrientation(100, 200);
        angle::Sleep(1000);

        // Draw in portrait orientation.
        glScissor(1, 1, 1, 1);
        glClearColor(1.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();

        // Check result from both draws.
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::red);
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Test that setting a surface to EGL_SINGLE_BUFFER after enabling
// EGL_FRONT_BUFFER_AUTO_REFRESH_ANDROID does not disable auto refresh
TEST_P(EGLAndroidAutoRefreshTest, Basic)
{
    ANGLE_SKIP_TEST_IF(
        !IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANDROID_front_buffer_auto_refresh"));
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));
    ANGLE_SKIP_TEST_IF(!IsAndroid());

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfig(&config, true));

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_BACK_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    EXPECT_EGL_TRUE(
        eglSurfaceAttrib(mDisplay, surface, EGL_FRONT_BUFFER_AUTO_REFRESH_ANDROID, EGL_TRUE));

    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));

    // Transition into EGL_SINGLE_BUFFER mode.
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    if (eglSwapBuffers(mDisplay, surface))
    {
        EGLint actualRenderbuffer;
        EXPECT_EGL_TRUE(eglQueryContext(mDisplay, context, EGL_RENDER_BUFFER, &actualRenderbuffer));
        EXPECT_EGL_TRUE(actualRenderbuffer == EGL_SINGLE_BUFFER);

        glClearColor(0.0, 1.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glFlush();
        // Flush should result in update of screen. Must be visually confirmed Green window.

        // Check color for automation.
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::green);

        // Switch back to EGL_BACK_BUFFER and check.
        EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_BACK_BUFFER));
        glClearColor(1.0, 1.0, 1.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_EGL_TRUE(eglSwapBuffers(mDisplay, surface));

        EXPECT_EGL_TRUE(eglQueryContext(mDisplay, context, EGL_RENDER_BUFFER, &actualRenderbuffer));
        EXPECT_EGL_TRUE(actualRenderbuffer == EGL_BACK_BUFFER);

        glClearColor(1.0, 0.0, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::red);
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Tests that CPU throttling unlocked call, added in the implicit swap buffers call, is executed.
TEST_P(EGLAndroidAutoRefreshTest, SwapCPUThrottling)
{
    ANGLE_SKIP_TEST_IF(
        !IsEGLDisplayExtensionEnabled(mDisplay, "EGL_ANDROID_front_buffer_auto_refresh"));
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));
    ANGLE_SKIP_TEST_IF(!IsAndroid());

    // Use high resolution to increase GPU load.
    const EGLint kWidth  = 2048;
    const EGLint kHeight = 2048;

    // These settings are expected to trigger CPU throttling in present.
    constexpr size_t kFrameFlushCount   = 8;
    constexpr GLuint kDrawInstanceCount = 256;

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfig(&config, true));

    EGLContext context = EGL_NO_CONTEXT;
    EXPECT_EGL_TRUE(createContext(config, &context));
    ASSERT_EGL_SUCCESS() << "eglCreateContext failed.";

    EGLSurface surface = EGL_NO_SURFACE;
    EXPECT_EGL_TRUE(
        createWindowSurface(config, mOSWindow->getNativeWindow(), &surface, EGL_SINGLE_BUFFER));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    EGLint actualRenderbuffer;
    EXPECT_EGL_TRUE(eglQueryContext(mDisplay, context, EGL_RENDER_BUFFER, &actualRenderbuffer));
    if (actualRenderbuffer == EGL_SINGLE_BUFFER)
    {
        // Enable auto refresh to prevent present from waiting on GPU.
        EXPECT_EGL_TRUE(
            eglSurfaceAttrib(mDisplay, surface, EGL_FRONT_BUFFER_AUTO_REFRESH_ANDROID, EGL_TRUE));

        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        glViewport(0, 0, kWidth, kHeight);

        for (size_t i = 0; i < kFrameFlushCount; ++i)
        {
            // Perform heavy draw call to load GPU.
            drawQuadInstanced(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, false,
                              kDrawInstanceCount);
            // This should cause implicit swap and possible CPU throttling in the tail call.
            glFlush();
        }

        // Tests same as the glFlush above.
        drawQuadInstanced(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, false,
                          kDrawInstanceCount);
        glFinish();
    }
    else
    {
        std::cout << "EGL_SINGLE_BUFFER mode is not supported." << std::endl;
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent - uncurrent failed.";

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, context);
    context = EGL_NO_CONTEXT;
}

// Tests that draw draw does not also synchronize read framebuffer binding.
TEST_P(EGLSurfaceTest, ReadFramebufferBindingSyncState)
{
    // This test uses functionality that is only available on Android
    ANGLE_SKIP_TEST_IF(!IsAndroid());

    const EGLint kPortraitWidth  = 128;
    const EGLint kPortraitHeight = 256;

    // Set portrait orientation before surface creation.
    mOSWindow->resize(kPortraitWidth, kPortraitHeight);
    mOSWindow->setOrientation(kPortraitWidth, kPortraitHeight);
    angle::Sleep(1000);

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // Make sure swapchain is created.
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();

    // Set landscape orientation after swapchain creation.  Note, this will not change window size.
    mOSWindow->setOrientation(kPortraitHeight, kPortraitWidth);
    angle::Sleep(1000);

    // Current swapchain is expected to be invalidated because of the present SUBOPTIMAL result.
    // This will invalidate draw and read framebuffer bindings context state dirty bits.
    eglSwapBuffers(mDisplay, mWindowSurface);
    ASSERT_EGL_SUCCESS();

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 64, 64);
    ASSERT_GL_NO_ERROR();

    // Bind only draw framebuffer, keeping default read framebuffer.
    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_DRAW_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    // Draw should not sync read framebuffer binding (at least not without read framebuffer object).
    ANGLE_GL_PROGRAM(drawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
    drawQuad(drawRed.get(), essl3_shaders::PositionAttrib(), 0.8f);
    ASSERT_GL_NO_ERROR();

    // Clear default framebuffer. This should sync both, read framebuffer object and binding.
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();

    // Check the entire surface.
    EXPECT_PIXEL_RECT_EQ(0, 0, kPortraitWidth, kPortraitHeight, GLColor::red);

    // Revert to the portrait orientation.
    mOSWindow->setOrientation(kPortraitWidth, kPortraitHeight);
    angle::Sleep(1000);
}

void EGLSurfaceTest::runWaitSemaphoreTest(bool useSecondContext)
{
    // Note: This test requires visual inspection for rendering artifacts.
    // However, absence of artifacts does not guarantee that there is no problem.

    initializeDisplay();

    constexpr int kInitialSize   = 64;
    constexpr int kWindowWidth   = 1080;
    constexpr int kWindowWHeight = 1920;

    mOSWindow->resize(kWindowWidth, kWindowWHeight);

    // Initialize an RGBA8 window and pbuffer surface
    constexpr EGLint kSurfaceAttributes[] = {EGL_RED_SIZE,     8,
                                             EGL_GREEN_SIZE,   8,
                                             EGL_BLUE_SIZE,    8,
                                             EGL_ALPHA_SIZE,   8,
                                             EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
                                             EGL_NONE};

    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;
    ASSERT_EGL_TRUE(eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ASSERT_NE(configCount, 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);
    ASSERT_NE(mPbufferSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_disjoint_timer_query"));

    if (useSecondContext)
    {
        ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
        initializeSingleContext(&mSecondContext, 0);
    }

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(program);
    ASSERT_GL_NO_ERROR();

    GLint posAttrib = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
    ASSERT_NE(posAttrib, -1);
    glEnableVertexAttribArray(posAttrib);
    ASSERT_GL_NO_ERROR();

    GLint colorUniformLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    constexpr int kFrameCount = 60 * 4;  // 4 sec @ 60Hz; 2 sec @ 120Hz;
    constexpr int kGridW      = 5;
    constexpr int kGridH      = 5;
    constexpr int kAnimDiv    = 20;

    for (int frame = 0; frame < kFrameCount; ++frame)
    {
        glClearColor(0.1f, 0.1f, 0.2f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        ASSERT_GL_NO_ERROR();

        for (int y = 0; y < kGridH; ++y)
        {
            // This should force "flushToPrimary()" each line in ANGLE
            GLuint query;
            glGenQueries(1, &query);
            ASSERT_GL_NO_ERROR();
            glBeginQuery(GL_TIME_ELAPSED_EXT, query);
            ASSERT_GL_NO_ERROR();

            for (int x = 0; x < kGridW; ++x)
            {
                const int xc        = (x + frame / kAnimDiv) % kGridW;
                const Vector4 color = {(xc + 0.5f) / kGridW, (y + 0.5f) / kGridH, 0.0f, 1.0f};

                const GLfloat x0 = (x + 0.1f) / kGridW * 2.0f - 1.0f;
                const GLfloat x1 = (x + 0.9f) / kGridW * 2.0f - 1.0f;
                const GLfloat y0 = (y + 0.1f) / kGridH * 2.0f - 1.0f;
                const GLfloat y1 = (y + 0.9f) / kGridH * 2.0f - 1.0f;

                std::array<Vector3, 6> vertexData;
                vertexData[0] = {x0, y1, 0.5f};
                vertexData[1] = {x0, y0, 0.5f};
                vertexData[2] = {x1, y1, 0.5f};
                vertexData[3] = {x1, y1, 0.5f};
                vertexData[4] = {x0, y0, 0.5f};
                vertexData[5] = {x1, y0, 0.5f};

                glUniform4f(colorUniformLocation, color.x(), color.y(), color.z(), color.w());
                glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 0, vertexData.data());
                glDrawArrays(GL_TRIANGLES, 0, 6);
                ASSERT_GL_NO_ERROR();
            }

            glEndQuery(GL_TIME_ELAPSED_EXT);
            glDeleteQueries(1, &query);
            ASSERT_GL_NO_ERROR();
        }

        if (useSecondContext)
        {
            std::thread([this] {
                eglBindAPI(EGL_OPENGL_ES_API);
                ASSERT_EGL_SUCCESS();
                eglMakeCurrent(mDisplay, mPbufferSurface, mPbufferSurface, mSecondContext);
                ASSERT_EGL_SUCCESS();
                glEnable(GL_SCISSOR_TEST);
                glScissor(0, 0, 1, 1);
                glClear(GL_COLOR_BUFFER_BIT);
                ASSERT_GL_NO_ERROR();
                eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
                ASSERT_EGL_SUCCESS();
            }).join();
        }
        else
        {
            eglMakeCurrent(mDisplay, mPbufferSurface, mPbufferSurface, mContext);
            ASSERT_EGL_SUCCESS();
            eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
            ASSERT_EGL_SUCCESS();
            eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
            ASSERT_EGL_SUCCESS();
        }

        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();
    }

    mOSWindow->resize(kInitialSize, kInitialSize);
}

// Test that there no artifacts because of the bug when wait semaphore could be added after
// rendering commands. This was possible by switching to Pbuffer surface and submit.
TEST_P(EGLSurfaceTest, DISABLED_WaitSemaphoreAddedAfterCommands)
{
    runWaitSemaphoreTest(false);
}

// Test that there no artifacts because of the bug when rendering commands could be submitted
// without adding wait semaphore. This was possible if submit commands from other thread.
TEST_P(EGLSurfaceTest, DISABLED_CommandsSubmittedWithoutWaitSemaphore)
{
    runWaitSemaphoreTest(true);
}

void EGLSurfaceTest::runDestroyNotCurrentSurfaceTest(bool testWindowsSurface)
{
    initializeDisplay();

    // Initialize an RGBA8 window and pbuffer surface
    constexpr EGLint kSurfaceAttributes[] = {EGL_RED_SIZE,     8,
                                             EGL_GREEN_SIZE,   8,
                                             EGL_BLUE_SIZE,    8,
                                             EGL_ALPHA_SIZE,   8,
                                             EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
                                             EGL_NONE};

    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;
    ASSERT_EGL_TRUE(eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ASSERT_NE(configCount, 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);
    ASSERT_NE(mPbufferSurface, EGL_NO_SURFACE);

    EGLSurface &testSurface  = testWindowsSurface ? mWindowSurface : mPbufferSurface;
    EGLSurface &otherSurface = testWindowsSurface ? mPbufferSurface : mWindowSurface;

    eglMakeCurrent(mDisplay, testSurface, testSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // Start RenderPass in the testSurface
    glEnable(GL_SCISSOR_TEST);
    glScissor(0, 0, 4, 4);
    glClearColor(0.5f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glDisable(GL_SCISSOR_TEST);
    ASSERT_GL_NO_ERROR();

    // Make other surface current keeping the context.
    // If bug present, the context may have unflushed work, related to the testSurface.
    eglMakeCurrent(mDisplay, otherSurface, otherSurface, mContext);
    ASSERT_EGL_SUCCESS();

    if (testWindowsSurface)
    {
        // This may flush Window Surface RenderPass
        glEnable(GL_SCISSOR_TEST);
        glScissor(0, 0, 4, 4);
        glClearColor(0.5f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glDisable(GL_SCISSOR_TEST);
        ASSERT_GL_NO_ERROR();
    }

    // Destroy the surface
    eglDestroySurface(mDisplay, testSurface);
    testSurface = EGL_NO_SURFACE;

    // This will submit all work (if bug present - include work related to the deleted testSurface).
    eglMakeCurrent(mDisplay, otherSurface, otherSurface, mContext);
    ASSERT_EGL_SUCCESS();
}

// Test that there is no crash because of the bug when not current PBuffer Surface destroyed, while
// there are still unflushed work in the Context.
TEST_P(EGLSurfaceTest, DestroyNotCurrentPbufferSurface)
{
    runDestroyNotCurrentSurfaceTest(false);
}

// Test that there is no crash because of the bug when not current Window Surface destroyed, while
// there are still unflushed work in the Context.
TEST_P(EGLSurfaceTest, DestroyNotCurrentWindowSurface)
{
    runDestroyNotCurrentSurfaceTest(true);
}

// Test that there is no tearing because of incorrect pipeline barriers
TEST_P(EGLSurfaceTest, DISABLED_RandomClearTearing)
{
    // Note: This test requires visual inspection for rendering artifacts.
    // However, absence of artifacts does not guarantee that there is no problem.

    initializeDisplay();

    constexpr int kInitialSize   = 64;
    constexpr int kWindowWidth   = 1080;
    constexpr int kWindowWHeight = 1920;

    mOSWindow->resize(kWindowWidth, kWindowWHeight);

    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    constexpr int kFrameCount = 60 * 4;  // 4 sec @ 60Hz; 2 sec @ 120Hz;

    for (int frame = 0; frame < kFrameCount; ++frame)
    {
        glClearColor(rand() % 256 / 255.0f, rand() % 256 / 255.0f, rand() % 256 / 255.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        ASSERT_GL_NO_ERROR();

        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();
    }

    mOSWindow->resize(kInitialSize, kInitialSize);
}

// Make sure a surface (from the same window) can be recreated after being destroyed, even if it's
// still current.
// This is to recreate the app behavior in https://issuetracker.google.com/292285899, which is
// not the correct spec behavior. It serves as a purpose to test the workaround feature
// uncurrent_egl_surface_upon_surface_destroy that is enabled only on vulkan backend to help
// the app get over the problem.
TEST_P(EGLSurfaceTest, DestroyAndRecreateWhileCurrent)
{
    setWindowVisible(mOSWindow, true);

    initializeDisplay();

    mConfig = chooseDefaultConfig(true);
    ASSERT_NE(mConfig, nullptr);

    EGLint surfaceType = EGL_NONE;
    eglGetConfigAttrib(mDisplay, mConfig, EGL_SURFACE_TYPE, &surfaceType);
    ASSERT_NE((surfaceType & EGL_WINDOW_BIT), 0);

    initializeWindowSurfaceWithAttribs(mConfig, {}, EGL_SUCCESS);
    initializeMainContext();

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // Draw with this surface to make sure it's used.
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glViewport(0, 0, 64, 64);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Destroy the surface while it's current; it won't actually be destroyed.
    eglDestroySurface(mDisplay, mWindowSurface);
    mWindowSurface = EGL_NO_SURFACE;

    // Create another surface from the same window right away.
    initializeWindowSurfaceWithAttribs(mConfig, {}, EGL_SUCCESS);

    // Make the new surface current; this leads to the actual destruction of the previous surface.
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext));
    ASSERT_EGL_SUCCESS();

    // Verify everything still works
    ANGLE_GL_PROGRAM(program2, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(program2, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ASSERT_GL_NO_ERROR();
}

// Regression test for a bug where destroying more than 2 surfaces during termination
// overflowed the unlocked tail call storage.
TEST_P(EGLSurfaceTest, CreateMultiWindowsSurfaceNoDestroy)
{
    initializeDisplay();

    // Initialize and create multi RGBA8 window surfaces
    constexpr EGLint kSurfaceAttributes[] = {EGL_RED_SIZE,     8,
                                             EGL_GREEN_SIZE,   8,
                                             EGL_BLUE_SIZE,    8,
                                             EGL_ALPHA_SIZE,   8,
                                             EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
                                             EGL_NONE};

    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;
    ASSERT_EGL_TRUE(eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ASSERT_NE(configCount, 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);

    // Create 3 window surfaces to trigger error
    std::vector<EGLint> windowAttributes;
    windowAttributes.push_back(EGL_NONE);

    for (int i = 0; i < 3; i++)
    {
        OSWindow *w = OSWindow::New();
        w->initialize("EGLSurfaceTest", 64, 64);

        eglCreateWindowSurface(mDisplay, mConfig, w->getNativeWindow(), windowAttributes.data());
        ASSERT_EGL_SUCCESS();
        mOtherWindows.push_back(w);
    }
}

// Test that querying EGL_RENDER_BUFFER of surface and context returns correct value.
// Context's render buffer should only change once eglSwapBuffers is called.
TEST_P(EGLSurfaceTest, QueryRenderBuffer)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));
    ANGLE_SKIP_TEST_IF(!IsAndroid());

    const EGLint configAttributes[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE,
                                       EGL_WINDOW_BIT | EGL_MUTABLE_RENDER_BUFFER_BIT_KHR,
                                       EGL_NONE};

    initializeDisplay();
    ANGLE_SKIP_TEST_IF(EGLWindow::FindEGLConfig(mDisplay, configAttributes, &mConfig) == EGL_FALSE);

    // Create window surface and make current
    mWindowSurface =
        eglCreateWindowSurface(mDisplay, mConfig, mOSWindow->getNativeWindow(), nullptr);
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(EGL_NO_SURFACE, mWindowSurface);

    initializeMainContext();
    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext));
    ASSERT_EGL_SUCCESS();

    // Set to single buffer mode and query the value
    ASSERT_EGL_TRUE(
        eglSurfaceAttrib(mDisplay, mWindowSurface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));

    EGLint queryRenderBuffer;
    ASSERT_EGL_TRUE(
        eglQuerySurface(mDisplay, mWindowSurface, EGL_RENDER_BUFFER, &queryRenderBuffer));
    ASSERT_EGL_SUCCESS();
    ASSERT_EQ(queryRenderBuffer, EGL_SINGLE_BUFFER);

    ASSERT_EGL_TRUE(eglQueryContext(mDisplay, mContext, EGL_RENDER_BUFFER, &queryRenderBuffer));
    ASSERT_EGL_SUCCESS();
    ASSERT_EQ(queryRenderBuffer, EGL_BACK_BUFFER);

    // Swap buffers and then query the value
    ASSERT_EGL_TRUE(eglSwapBuffers(mDisplay, mWindowSurface));
    ASSERT_EGL_SUCCESS();

    ASSERT_EGL_TRUE(
        eglQuerySurface(mDisplay, mWindowSurface, EGL_RENDER_BUFFER, &queryRenderBuffer));
    ASSERT_EGL_SUCCESS();
    ASSERT_EQ(queryRenderBuffer, EGL_SINGLE_BUFFER);

    ASSERT_EGL_TRUE(eglQueryContext(mDisplay, mContext, EGL_RENDER_BUFFER, &queryRenderBuffer));
    ASSERT_EGL_SUCCESS();
    ASSERT_EQ(queryRenderBuffer, EGL_SINGLE_BUFFER);

    ASSERT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
    ASSERT_EGL_TRUE(eglDestroySurface(mDisplay, mWindowSurface));
    mWindowSurface = EGL_NO_SURFACE;
    ASSERT_EGL_TRUE(eglDestroyContext(mDisplay, mContext));
    mContext = EGL_NO_CONTEXT;
    ASSERT_EGL_SUCCESS();
}

// Test that new API eglQuerySupportedCompressionRatesEXT could work, and
// validation for the API should also work. If any rate can be queried, then use
// that rate to create window surface. Query the surface's compression rate
// should get the expected rate, and a simple draw should succeed on the surface.
TEST_P(EGLSurfaceTest, SurfaceFixedRateCompression)
{
    initializeDisplay();
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_surface_compression"));
    // Initialize an RGBA8 window and pbuffer surface
    constexpr EGLint kSurfaceAttributes[] = {EGL_RED_SIZE,
                                             8,
                                             EGL_GREEN_SIZE,
                                             8,
                                             EGL_BLUE_SIZE,
                                             8,
                                             EGL_ALPHA_SIZE,
                                             8,
                                             EGL_SURFACE_TYPE,
                                             EGL_WINDOW_BIT,
                                             EGL_RENDERABLE_TYPE,
                                             EGL_OPENGL_ES2_BIT,
                                             EGL_NONE};
    EGLint configCount                    = 0;
    EGLint numRates                       = 0;
    EXPECT_EGL_TRUE(eglChooseConfig(mDisplay, kSurfaceAttributes, &mConfig, 1, &configCount));
    ASSERT_NE(configCount, 0);
    ASSERT_NE(mConfig, nullptr);
    // Fail, invalid display
    EXPECT_EGL_FALSE(eglQuerySupportedCompressionRatesEXT(EGL_NO_DISPLAY, mConfig, nullptr, nullptr,
                                                          0, &numRates));
    ASSERT_EGL_ERROR(EGL_BAD_DISPLAY);
    // Fail, rate_size < 0
    EXPECT_EGL_FALSE(
        eglQuerySupportedCompressionRatesEXT(mDisplay, mConfig, nullptr, nullptr, -1, &numRates));
    ASSERT_EGL_ERROR(EGL_BAD_PARAMETER);
    // Fail, pointer rates is nullptr
    EXPECT_EGL_FALSE(
        eglQuerySupportedCompressionRatesEXT(mDisplay, mConfig, nullptr, nullptr, 1, &numRates));
    ASSERT_EGL_ERROR(EGL_BAD_PARAMETER);
    // Fail, return num_rates is nullptr
    EXPECT_EGL_FALSE(
        eglQuerySupportedCompressionRatesEXT(mDisplay, mConfig, nullptr, nullptr, 0, nullptr));
    ASSERT_EGL_ERROR(EGL_BAD_PARAMETER);
    EGLint rates[3];
    // Success, actual values of rates are depended on each platform
    EXPECT_EGL_TRUE(
        eglQuerySupportedCompressionRatesEXT(mDisplay, mConfig, nullptr, rates, 3, &numRates));
    ASSERT_EGL_SUCCESS();

    if (numRates > 0 && rates[0] != EGL_SURFACE_COMPRESSION_FIXED_RATE_NONE_EXT)
    {
        // If any rate can be queried, then use that rate to create window surface and test
        std::vector<EGLint> winSurfaceAttribs;
        winSurfaceAttribs.push_back(EGL_SURFACE_COMPRESSION_EXT);
        winSurfaceAttribs.push_back(rates[0]);
        // Create window surface using the selected rate.
        initializeWindowSurfaceWithAttribs(mConfig, winSurfaceAttribs, EGL_SUCCESS);
        ASSERT_EGL_SUCCESS();
        ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);
        EGLint selectedRate;
        ASSERT_EGL_TRUE(
            eglQuerySurface(mDisplay, mWindowSurface, EGL_SURFACE_COMPRESSION_EXT, &selectedRate));
        ASSERT_EGL_SUCCESS();
        ASSERT_EQ(selectedRate, rates[0]);
        initializeMainContext();
        EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext));
        ASSERT_EGL_SUCCESS();
        // Make sure the surface works. Draw red and verify.
        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        glUseProgram(program);
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
        ASSERT_EGL_TRUE(eglSwapBuffers(mDisplay, mWindowSurface));

        EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, nullptr, nullptr, mContext));
        EXPECT_EGL_TRUE(eglDestroySurface(mDisplay, mWindowSurface));
        mWindowSurface = EGL_NO_SURFACE;

        // Create another surface using default rate.
        winSurfaceAttribs.back() = EGL_SURFACE_COMPRESSION_FIXED_RATE_DEFAULT_EXT;
        initializeWindowSurfaceWithAttribs(mConfig, winSurfaceAttribs, EGL_SUCCESS);
        ASSERT_EGL_SUCCESS();
        ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);
        selectedRate = EGL_SURFACE_COMPRESSION_FIXED_RATE_NONE_EXT;
        ASSERT_EGL_TRUE(
            eglQuerySurface(mDisplay, mWindowSurface, EGL_SURFACE_COMPRESSION_EXT, &selectedRate));
        ASSERT_EGL_SUCCESS();
        ASSERT_NE(selectedRate, EGL_SURFACE_COMPRESSION_FIXED_RATE_NONE_EXT);
        EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext));
        ASSERT_EGL_SUCCESS();
        // Make sure the surface works. Draw red and verify.
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
        ASSERT_EGL_TRUE(eglSwapBuffers(mDisplay, mWindowSurface));
    }
}

// Test the validation errors for eglSetDamageRegionKHR
TEST_P(EGLSurfaceTest, SetDamageRegionNegativeValidation)
{
    initializeDisplay();
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_partial_update"));

    constexpr EGLint kSurfaceAttributes[] = {EGL_RED_SIZE,     8,
                                             EGL_GREEN_SIZE,   8,
                                             EGL_BLUE_SIZE,    8,
                                             EGL_ALPHA_SIZE,   8,
                                             EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                                             EGL_NONE};

    EGLint age              = 0;
    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;

    ASSERT_EGL_TRUE(eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ASSERT_NE(configCount, 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);
    initializeAllContexts();
    EXPECT_EGL_SUCCESS();
    EXPECT_NE(mWindowSurface, EGL_NO_SURFACE);
    EXPECT_NE(mPbufferSurface, EGL_NO_SURFACE);

    // Fail: surface is not a postable surface
    EXPECT_EGL_FALSE(eglSetDamageRegionKHR(mDisplay, mPbufferSurface, nullptr, 0));
    EXPECT_EGL_ERROR(EGL_BAD_MATCH);

    // Fail: surface is not the current draw surface for the calling thread
    EXPECT_EGL_FALSE(eglSetDamageRegionKHR(mDisplay, mWindowSurface, nullptr, 0));
    EXPECT_EGL_ERROR(EGL_BAD_MATCH);

    EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mSecondContext));
    EXPECT_EGL_SUCCESS();

    // Fail: EGL_BUFFER_AGE_KHR attribute of surface has not been queried since the most recent
    // frame boundary
    EXPECT_EGL_FALSE(eglSetDamageRegionKHR(mDisplay, mWindowSurface, nullptr, 0));
    EXPECT_EGL_ERROR(EGL_BAD_ACCESS);

    // Perform empty swap
    EXPECT_EGL_TRUE(eglSwapBuffers(mDisplay, mWindowSurface));
    EXPECT_EGL_SUCCESS();

    EXPECT_EGL_TRUE(eglQuerySurface(mDisplay, mWindowSurface, EGL_BUFFER_AGE_KHR, &age));
    EXPECT_EGL_SUCCESS();
    EXPECT_GE(age, 0);

    // Fail: n_rects should be a valid value
    EXPECT_EGL_FALSE(eglSetDamageRegionKHR(mDisplay, mWindowSurface, nullptr, -1));
    EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);

    // Success
    EXPECT_EGL_TRUE(eglSetDamageRegionKHR(mDisplay, mWindowSurface, nullptr, 0));
    EXPECT_EGL_SUCCESS();

    // Fail: damage region has already been set on surface since the most recent frame boundary
    EXPECT_EGL_FALSE(eglSetDamageRegionKHR(mDisplay, mWindowSurface, nullptr, 0));
    EXPECT_EGL_ERROR(EGL_BAD_ACCESS);
}

// Verifies that swapchain image is transitioned into the Present layout after MSAA resolve,
// regardless if it was performed as part of the renderpass or by using an outside renderpass
// command.
TEST_P(EGLSurfaceTest, PresentLayoutTransitionWithMSAA)
{
    initializeDisplay();

    constexpr EGLint kInitialSize = 64;

    // Initialize an RGBA8 window surface with 4x MSAA
    constexpr EGLint kSurfaceAttributes[] = {EGL_RED_SIZE,
                                             8,
                                             EGL_GREEN_SIZE,
                                             8,
                                             EGL_BLUE_SIZE,
                                             8,
                                             EGL_ALPHA_SIZE,
                                             8,
                                             EGL_SAMPLE_BUFFERS,
                                             1,
                                             EGL_SAMPLES,
                                             4,
                                             EGL_SURFACE_TYPE,
                                             EGL_WINDOW_BIT,
                                             EGL_NONE};

    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;
    ANGLE_SKIP_TEST_IF(
        !eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ANGLE_SKIP_TEST_IF(configCount == 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);
    initializeMainContext();
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glUseProgram(program);

    glViewport(0, 0, kInitialSize, kInitialSize);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    // Tests that swapchain image layout is in Present layout after resolve with renderpass.
    eglSwapBuffers(mDisplay, mWindowSurface);
    ASSERT_EGL_SUCCESS();

    glEnable(GL_SCISSOR_TEST);
    glScissor(0, 0, kInitialSize / 2, kInitialSize / 2);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    // Tests that swapchain image layout is in Present layout after outside renderpass resolve.
    eglSwapBuffers(mDisplay, mWindowSurface);
    ASSERT_EGL_SUCCESS();
}

// Test resolve a 4xMSAA surface that supports a EGLConfig without alpha support
TEST_P(EGLSurfaceTest, MSAAResolveWithEGLConfig8880)
{
    initializeDisplay();

    // Initialize an RGB8 window surface with 4x MSAA
    constexpr EGLint kSurfaceAttributes[] = {EGL_RED_SIZE,
                                             8,
                                             EGL_GREEN_SIZE,
                                             8,
                                             EGL_BLUE_SIZE,
                                             8,
                                             EGL_ALPHA_SIZE,
                                             0,
                                             EGL_SAMPLE_BUFFERS,
                                             1,
                                             EGL_SAMPLES,
                                             4,
                                             EGL_SURFACE_TYPE,
                                             EGL_WINDOW_BIT,
                                             EGL_NONE};

    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;
    ANGLE_SKIP_TEST_IF(
        !eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ANGLE_SKIP_TEST_IF(configCount == 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);
    initializeMainContext();
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glUseProgram(program);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    // Tests resolve during read pixels.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Tests outside renderpass resolve during swap.
    eglSwapBuffers(mDisplay, mWindowSurface);
    ASSERT_EGL_SUCCESS();

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    // Tests renderpass resolve during swap.
    eglSwapBuffers(mDisplay, mWindowSurface);
    ASSERT_EGL_SUCCESS();
}

// Regression test for a bug in the Vulkan backend where a staged clear was not applied if
// glGetMultisamplefv is called.
TEST_P(EGLSurfaceTest, GetMultisamplefvAfterClear)
{
    initializeDisplay();

    // Initialize an RGBA8 window surface with 4x MSAA
    constexpr EGLint kSurfaceAttributes[] = {EGL_RED_SIZE,
                                             8,
                                             EGL_GREEN_SIZE,
                                             8,
                                             EGL_BLUE_SIZE,
                                             8,
                                             EGL_ALPHA_SIZE,
                                             8,
                                             EGL_SAMPLE_BUFFERS,
                                             1,
                                             EGL_SAMPLES,
                                             4,
                                             EGL_SURFACE_TYPE,
                                             EGL_WINDOW_BIT,
                                             EGL_NONE};

    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;
    ANGLE_SKIP_TEST_IF(
        !eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ANGLE_SKIP_TEST_IF(configCount == 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);
    initializeMainContext();
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_texture_multisample"));

    // Clear operation will be staged (Vulkan backend).
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Calling this API will perform draw framebuffer sync which will flush staged clear.
    // If there is no bug, clear will not be deferred during the flush.
    GLfloat samplePosition[2];
    glGetMultisamplefvANGLE(GL_SAMPLE_POSITION, 0, samplePosition);
    ASSERT_GL_NO_ERROR();

    // Prepare auxiliary framebuffer.
    GLFramebuffer framebuffer;
    GLRenderbuffer renderBuffer;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 50, 50);
    glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                              renderBuffer);
    EXPECT_GL_NO_ERROR();

    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Check default framebuffer contains expected value.
    // In case of a bug, previously deferred clear will not be applied.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

int EGLSurfaceTest::drawSizeCheckRect(EGLSurface surface,
                                      GLsizei checkRectWidth,
                                      GLsizei checkRectHeight)
{
    glDisable(GL_SCISSOR_TEST);
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glEnable(GL_SCISSOR_TEST);
    glScissor(1, 1, checkRectWidth - 2, checkRectHeight - 2);
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_GL_NO_ERROR();

    const angle::GLColor referenceColors[8] = {GLColor::green, GLColor::green, GLColor::red,
                                               GLColor::red,   GLColor::red,   GLColor::red,
                                               GLColor::green, GLColor::green};
    angle::GLColor surfaceColors[std::size(referenceColors)];

    // Pixels are listed in a scanning order (left->right, bottom->top).
    surfaceColors[0] = angle::ReadColor(0, 0);
    surfaceColors[1] = angle::ReadColor(checkRectWidth - 1, 0);
    surfaceColors[2] = angle::ReadColor(1, 1);
    surfaceColors[3] = angle::ReadColor(checkRectWidth - 2, 1);
    surfaceColors[4] = angle::ReadColor(1, checkRectHeight - 2);
    surfaceColors[5] = angle::ReadColor(checkRectWidth - 2, checkRectHeight - 2);
    surfaceColors[6] = angle::ReadColor(0, checkRectHeight - 1);
    surfaceColors[7] = angle::ReadColor(checkRectWidth - 1, checkRectHeight - 1);
    EXPECT_GL_NO_ERROR();

    EXPECT_EQ(surfaceColors[0], referenceColors[0]);
    EXPECT_EQ(surfaceColors[1], referenceColors[1]);
    EXPECT_EQ(surfaceColors[2], referenceColors[2]);
    EXPECT_EQ(surfaceColors[3], referenceColors[3]);
    EXPECT_EQ(surfaceColors[4], referenceColors[4]);
    EXPECT_EQ(surfaceColors[5], referenceColors[5]);
    EXPECT_EQ(surfaceColors[6], referenceColors[6]);
    EXPECT_EQ(surfaceColors[7], referenceColors[7]);

    int result = 0;
    for (size_t i = 0; i < std::size(referenceColors); ++i)
    {
        result += ANGLE_UNSAFE_TODO(surfaceColors[i] != referenceColors[i] ? 1 : 0);
    }

    // Surface size must not change after the draw.
    EGLint surfaceWidth  = 0;
    EGLint surfaceHeight = 0;
    eglQuerySurface(mDisplay, surface, EGL_WIDTH, &surfaceWidth);
    eglQuerySurface(mDisplay, surface, EGL_HEIGHT, &surfaceHeight);
    EXPECT_EGL_SUCCESS();
    EXPECT_EQ(surfaceWidth, checkRectWidth);
    EXPECT_EQ(surfaceHeight, checkRectHeight);

    result += (surfaceWidth != checkRectWidth) ? 10 : 0;
    result += (surfaceHeight != checkRectHeight) ? 10 : 0;

    return result;
}

// Tests window surface resize after swap that follows window resize.
TEST_P(EGLSurfaceTest, ResizeAfterSwap)
{
    // http://anglebug.com/42263074
    ANGLE_SKIP_TEST_IF(IsLinux() && IsARM());

    constexpr size_t kSizeCount = 2;
    constexpr std::array<int, kSizeCount> kWidths{199, 500};
    constexpr std::array<int, kSizeCount> kHeights{499, 200};

    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    for (size_t i = 0; i < kSizeCount; ++i)
    {
        // Resize before swap.
        mOSWindow->resize(kWidths[i], kHeights[i]);

        // Swap must resize the surface if it is not already resized.
        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();

        EGLint surfaceWidth  = 0;
        EGLint surfaceHeight = 0;

        // Surface must have window size after swap.
        eglQuerySurface(mDisplay, mWindowSurface, EGL_WIDTH, &surfaceWidth);
        eglQuerySurface(mDisplay, mWindowSurface, EGL_HEIGHT, &surfaceHeight);
        ASSERT_EGL_SUCCESS();
        EXPECT_EQ(surfaceWidth, kWidths[i]);
        EXPECT_EQ(surfaceHeight, kHeights[i]);

        // Actual buffer size must match the reported size.
        EXPECT_EQ(drawSizeCheckRect(mWindowSurface, surfaceWidth, surfaceHeight), 0);
    }
}

// Tests window surface resize after swap that follows window resize, while skipping size query.
TEST_P(EGLSurfaceTest, ResizeAfterSwapSkipSizeQuery)
{
    // http://anglebug.com/42263074
    ANGLE_SKIP_TEST_IF(IsLinux() && IsARM());

    constexpr size_t kSizeCount = 2;
    constexpr std::array<int, kSizeCount> kWidths{199, 500};
    constexpr std::array<int, kSizeCount> kHeights{499, 200};

    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    for (size_t i = 0; i < kSizeCount; ++i)
    {
        // Resize before swap.
        mOSWindow->resize(kWidths[i], kHeights[i]);

        // Swap must resize the surface if it is not already resized.
        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();

        // Actual buffer size must match the reported size.
        EXPECT_EQ(drawSizeCheckRect(mWindowSurface, kWidths[i], kHeights[i]), 0);
    }
}

// Tests window surface resize when window is resized before draw and swap.
TEST_P(EGLSurfaceTest, ResizeBeforeDraw)
{
    // http://anglebug.com/42263074
    ANGLE_SKIP_TEST_IF(IsLinux() && IsARM());

    constexpr size_t kSizeCount = 3;
    constexpr std::array<int, kSizeCount> kWidths{64, 199, 500};
    constexpr std::array<int, kSizeCount> kHeights{64, 499, 200};

    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    for (size_t i = 1; i < kSizeCount; ++i)
    {
        // Resize after create/swap (before draw).
        mOSWindow->resize(kWidths[i], kHeights[i]);

        EGLint surfaceWidth  = 0;
        EGLint surfaceHeight = 0;

        // Some platforms may resize the surface before draw, while others may wait until swap.
        eglQuerySurface(mDisplay, mWindowSurface, EGL_WIDTH, &surfaceWidth);
        eglQuerySurface(mDisplay, mWindowSurface, EGL_HEIGHT, &surfaceHeight);
        ASSERT_EGL_SUCCESS();
        if (surfaceWidth == kWidths[i])
        {
            EXPECT_EQ(surfaceHeight, kHeights[i]);
        }
        else
        {
            EXPECT_EQ(surfaceWidth, kWidths[i - 1]);
            EXPECT_EQ(surfaceHeight, kHeights[i - 1]);
        }

        // Actual buffer size must match the reported size.
        EXPECT_EQ(drawSizeCheckRect(mWindowSurface, surfaceWidth, surfaceHeight), 0);

        // Start a new frame.
        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();
    }
}

// Tests window surface resize when window is resized before draw and swap, while delaying size
// query after the draw.
TEST_P(EGLSurfaceTest, ResizeBeforeDrawPostSizeQuery)
{
    // http://anglebug.com/42263074
    ANGLE_SKIP_TEST_IF(IsLinux() && IsARM());

    constexpr size_t kSizeCount = 3;
    constexpr std::array<int, kSizeCount> kWidths{64, 199, 500};
    constexpr std::array<int, kSizeCount> kHeights{64, 499, 200};

    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    for (size_t i = 1; i < kSizeCount; ++i)
    {
        // Resize after create/swap (before draw).
        mOSWindow->resize(kWidths[i], kHeights[i]);

        // A warm-up draw in order to acquire the back buffer image before size query.
        glEnable(GL_SCISSOR_TEST);
        glScissor(1, 1, 1, 1);
        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_GL_NO_ERROR();

        EGLint surfaceWidth  = 0;
        EGLint surfaceHeight = 0;

        // Some platforms may resize the surface, while others may wait until swap.
        eglQuerySurface(mDisplay, mWindowSurface, EGL_WIDTH, &surfaceWidth);
        eglQuerySurface(mDisplay, mWindowSurface, EGL_HEIGHT, &surfaceHeight);
        ASSERT_EGL_SUCCESS();
        if (surfaceWidth == kWidths[i])
        {
            EXPECT_EQ(surfaceHeight, kHeights[i]);
        }
        else
        {
            EXPECT_EQ(surfaceWidth, kWidths[i - 1]);
            EXPECT_EQ(surfaceHeight, kHeights[i - 1]);
        }

        // Actual buffer size must match the reported size.
        EXPECT_EQ(drawSizeCheckRect(mWindowSurface, surfaceWidth, surfaceHeight), 0);

        // Start a new frame.
        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();
    }
}

// Tests window surface resize when window is resized after draw but before swap.
TEST_P(EGLSurfaceTest, ResizeAfterDraw)
{
    // http://anglebug.com/42263074
    ANGLE_SKIP_TEST_IF(IsLinux() && IsARM());

    constexpr size_t kSizeCount = 3;
    constexpr std::array<int, kSizeCount> kWidths{64, 199, 500};
    constexpr std::array<int, kSizeCount> kHeights{64, 499, 200};

    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // http://anglebug.com/397848903
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsAdreno() && IsOpenGLES());

    for (size_t i = 1; i < kSizeCount; ++i)
    {
        // Actual buffer size must match the window size.
        EXPECT_EQ(drawSizeCheckRect(mWindowSurface, kWidths[i - 1], kHeights[i - 1]), 0);

        // Resize after draw (before swap).
        mOSWindow->resize(kWidths[i], kHeights[i]);

        EGLint surfaceWidth  = 0;
        EGLint surfaceHeight = 0;

        // Some platforms may resize the surface after draw, while others may wait until swap.
        eglQuerySurface(mDisplay, mWindowSurface, EGL_WIDTH, &surfaceWidth);
        eglQuerySurface(mDisplay, mWindowSurface, EGL_HEIGHT, &surfaceHeight);
        ASSERT_EGL_SUCCESS();
        if (surfaceWidth == kWidths[i])
        {
            EXPECT_EQ(surfaceHeight, kHeights[i]);
        }
        else
        {
            EXPECT_EQ(surfaceWidth, kWidths[i - 1]);
            EXPECT_EQ(surfaceHeight, kHeights[i - 1]);
        }

        // Actual buffer size must match the reported size.
        EXPECT_EQ(drawSizeCheckRect(mWindowSurface, surfaceWidth, surfaceHeight), 0);

        // Start a new frame.
        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();
    }
}

// Tests window surface resize using large window sizes.
TEST_P(EGLSurfaceTest, ResizeLargeWindow)
{
    // http://anglebug.com/42263074
    ANGLE_SKIP_TEST_IF(IsLinux() && IsARM());

    constexpr size_t kSizeCount = 2;
    constexpr std::array<int, kSizeCount> kWidths{1079, 999};
    constexpr std::array<int, kSizeCount> kHeights{999, 1079};

    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    for (size_t i = 0; i < kSizeCount; ++i)
    {
        // Resize before swap.
        mOSWindow->resize(kWidths[i], kHeights[i]);

        // Swap must resize the surface if it is not already resized.
        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();

        // A warm-up draw in order to acquire the back buffer image before size query.
        glEnable(GL_SCISSOR_TEST);
        glScissor(1, 1, 1, 1);
        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_GL_NO_ERROR();

        EGLint surfaceWidth  = 0;
        EGLint surfaceHeight = 0;

        // Surface must have window size after swap and draw.
        eglQuerySurface(mDisplay, mWindowSurface, EGL_WIDTH, &surfaceWidth);
        eglQuerySurface(mDisplay, mWindowSurface, EGL_HEIGHT, &surfaceHeight);
        ASSERT_EGL_SUCCESS();
        EXPECT_EQ(surfaceWidth, kWidths[i]);
        EXPECT_EQ(surfaceHeight, kHeights[i]);

        // Actual buffer size must match the reported size.
        EXPECT_EQ(drawSizeCheckRect(mWindowSurface, surfaceWidth, surfaceHeight), 0);
    }
}

// Tests that window surface resize when window is invisible does not cause failure.
TEST_P(EGLSurfaceTest, ResizeInvisibleWindow)
{
    // http://anglebug.com/42263074
    ANGLE_SKIP_TEST_IF(IsLinux() && IsARM());

    constexpr size_t kSizeCount = 2;
    constexpr std::array<int, kSizeCount> kWidths{199, 500};
    constexpr std::array<int, kSizeCount> kHeights{499, 200};

    setWindowVisible(mOSWindow, false);

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    for (size_t i = 0; i < kSizeCount; ++i)
    {
        // Resize before swap.
        mOSWindow->resize(kWidths[i], kHeights[i]);

        // Swap should resize the surface if it is not already resized.
        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();

        // A warm-up draw in order to acquire the back buffer image before size query.
        glEnable(GL_SCISSOR_TEST);
        glScissor(1, 1, 1, 1);
        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_GL_NO_ERROR();

        EGLint surfaceHeight = 0;
        EGLint surfaceWidth  = 0;

        // Query surface size but skip the check since size may not change when window is invisible.
        eglQuerySurface(mDisplay, mWindowSurface, EGL_WIDTH, &surfaceWidth);
        eglQuerySurface(mDisplay, mWindowSurface, EGL_HEIGHT, &surfaceHeight);
        ASSERT_EGL_SUCCESS();

        // Actual buffer size must match the reported size.
        EXPECT_EQ(drawSizeCheckRect(mWindowSurface, surfaceWidth, surfaceHeight), 0);
    }
}

// Tests that making context current after window surface resize sets correct viewport size.
TEST_P(EGLSurfaceTest, ResizeBeforeMakeCurrent)
{
    // http://anglebug.com/42263074
    ANGLE_SKIP_TEST_IF(IsLinux() && IsARM());

    constexpr size_t kSizeCount = 2;
    constexpr std::array<int, kSizeCount> kWidths{64, 199};
    constexpr std::array<int, kSizeCount> kHeights{64, 499};

    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    // Resize before make current.
    mOSWindow->resize(kWidths[1], kHeights[1]);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    EGLint surfaceWidth  = 0;
    EGLint surfaceHeight = 0;

    // Some platforms may resize the surface, while others may wait until swap.
    eglQuerySurface(mDisplay, mWindowSurface, EGL_WIDTH, &surfaceWidth);
    eglQuerySurface(mDisplay, mWindowSurface, EGL_HEIGHT, &surfaceHeight);
    ASSERT_EGL_SUCCESS();
    if (surfaceWidth == kWidths[1])
    {
        EXPECT_EQ(surfaceHeight, kHeights[1]);
    }
    else
    {
        EXPECT_EQ(surfaceWidth, kWidths[0]);
        EXPECT_EQ(surfaceHeight, kHeights[0]);
    }

    // Viewport should match the reported surface size.
    struct
    {
        GLint x, y, width, height;
    } vp;
    glGetIntegerv(GL_VIEWPORT, &vp.x);
    EXPECT_EQ(vp.x, 0);
    EXPECT_EQ(vp.y, 0);
    EXPECT_EQ(vp.width, surfaceWidth);
    EXPECT_EQ(vp.height, surfaceHeight);

    // Draw after the size query.
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    // Draw should cover the entire viewport.
    EXPECT_PIXEL_COLOR_EQ(vp.x, vp.y, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(vp.x + vp.width - 1, vp.y, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(vp.x, vp.y + vp.height - 1, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(vp.x + vp.width - 1, vp.y + vp.height - 1, GLColor::blue);

    // Actual buffer size must match the reported size.
    EXPECT_EQ(drawSizeCheckRect(mWindowSurface, surfaceWidth, surfaceHeight), 0);
}

// Tests that making context current after window surface resize sets correct viewport size, while
// delaying size query after the draw.
TEST_P(EGLSurfaceTest, ResizeBeforeMakeCurrentPostSizeQuery)
{
    // http://anglebug.com/42263074
    ANGLE_SKIP_TEST_IF(IsLinux() && IsARM());

    constexpr size_t kSizeCount = 2;
    constexpr std::array<int, kSizeCount> kWidths{64, 199};
    constexpr std::array<int, kSizeCount> kHeights{64, 499};

    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    // Resize before make current.
    mOSWindow->resize(kWidths[1], kHeights[1]);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // Draw before the size query to acquire the back buffer image.
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    EGLint surfaceWidth  = 0;
    EGLint surfaceHeight = 0;

    // Some platforms may resize the surface, while others may wait until swap.
    eglQuerySurface(mDisplay, mWindowSurface, EGL_WIDTH, &surfaceWidth);
    eglQuerySurface(mDisplay, mWindowSurface, EGL_HEIGHT, &surfaceHeight);
    ASSERT_EGL_SUCCESS();
    if (surfaceWidth == kWidths[1])
    {
        EXPECT_EQ(surfaceHeight, kHeights[1]);
    }
    else
    {
        EXPECT_EQ(surfaceWidth, kWidths[0]);
        EXPECT_EQ(surfaceHeight, kHeights[0]);
    }

    // Viewport should match the reported surface size.
    struct
    {
        GLint x, y, width, height;
    } vp;
    glGetIntegerv(GL_VIEWPORT, &vp.x);
    EXPECT_EQ(vp.x, 0);
    EXPECT_EQ(vp.y, 0);
    EXPECT_EQ(vp.width, surfaceWidth);
    EXPECT_EQ(vp.height, surfaceHeight);

    // Draw should cover the entire viewport.
    EXPECT_PIXEL_COLOR_EQ(vp.x, vp.y, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(vp.x + vp.width - 1, vp.y, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(vp.x, vp.y + vp.height - 1, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(vp.x + vp.width - 1, vp.y + vp.height - 1, GLColor::blue);

    // Actual buffer size must match the reported size.
    EXPECT_EQ(drawSizeCheckRect(mWindowSurface, surfaceWidth, surfaceHeight), 0);
}

// Tests window surface resize is correctly tracked by glReadPixelsRobustANGLE.
TEST_P(EGLSurfaceTest, ResizeAndReadPixelsRobustANGLE)
{
    // http://anglebug.com/42263074
    ANGLE_SKIP_TEST_IF(IsLinux() && IsARM());

    constexpr size_t kSizeCount = 2;
    constexpr std::array<int, kSizeCount> kWidths{199, 500};
    constexpr std::array<int, kSizeCount> kHeights{499, 200};

    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();
    initializeSurfaceWithDefaultConfig(true);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_robust_client_memory"));

    // This is here just to acquire the back buffer.
    glClear(GL_COLOR_BUFFER_BIT);

    for (size_t i = 0; i < kSizeCount; ++i)
    {
        // Resize before swap.
        mOSWindow->resize(kWidths[i], kHeights[i]);

        // Swap must resize the surface if it is not already resized.
        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();

        std::vector<GLubyte> rgbaData(kWidths[i] * kHeights[i] * 4);

        // We should be able to read the entire buffer using the new size.
        GLsizei length = 0;
        GLsizei width  = 0;
        GLsizei height = 0;
        glReadPixelsRobustANGLE(0, 0, kWidths[i], kHeights[i], GL_RGBA, GL_UNSIGNED_BYTE,
                                static_cast<GLsizei>(rgbaData.size()), &length, &width, &height,
                                rgbaData.data());
        EXPECT_GL_NO_ERROR();
        EXPECT_EQ(static_cast<GLsizei>(rgbaData.size()), length);
        EXPECT_EQ(kWidths[i], width);
        EXPECT_EQ(kHeights[i], height);
    }
}

// Tests window surface resize is correctly tracked by glBlitFramebufferANGLE.
TEST_P(EGLSurfaceTest, ResizeAndBlitFramebufferANGLE)
{
    // http://anglebug.com/42263074
    ANGLE_SKIP_TEST_IF(IsLinux() && IsARM());

    constexpr size_t kSizeCount = 2;
    constexpr std::array<int, kSizeCount> kWidths{199, 500};
    constexpr std::array<int, kSizeCount> kHeights{499, 200};

    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();

    // Initialize an RGBA8 window surface with 4x MSAA
    constexpr EGLint kSurfaceAttributes[] = {EGL_RED_SIZE,
                                             8,
                                             EGL_GREEN_SIZE,
                                             8,
                                             EGL_BLUE_SIZE,
                                             8,
                                             EGL_ALPHA_SIZE,
                                             8,
                                             EGL_SAMPLE_BUFFERS,
                                             1,
                                             EGL_SAMPLES,
                                             4,
                                             EGL_SURFACE_TYPE,
                                             EGL_WINDOW_BIT,
                                             EGL_NONE};

    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;
    ANGLE_SKIP_TEST_IF(
        !eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ANGLE_SKIP_TEST_IF(configCount == 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_framebuffer_blit"));

    // This is here just to acquire the back buffer.
    glClear(GL_COLOR_BUFFER_BIT);

    for (size_t i = 0; i < kSizeCount; ++i)
    {
        // Resize before swap.
        mOSWindow->resize(kWidths[i], kHeights[i]);

        // Swap must resize the surface if it is not already resized.
        eglSwapBuffers(mDisplay, mWindowSurface);
        ASSERT_EGL_SUCCESS();

        // Create render target texture.
        GLTexture color;
        glBindTexture(GL_TEXTURE_2D, color);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidths[i], kHeights[i], 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);

        // Create single sampled draw framebuffer.
        GLFramebuffer fbo;
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
        ASSERT_GL_NO_ERROR();
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_DRAW_FRAMEBUFFER);

        // This should not generate following error:
        //   "Only whole-buffer blit is supported from a multisampled read buffer in this extension"
        glBlitFramebufferANGLE(0, 0, kWidths[i], kHeights[i], 0, 0, kWidths[i], kHeights[i],
                               GL_COLOR_BUFFER_BIT, GL_NEAREST);
        EXPECT_GL_NO_ERROR();
    }
}

class EGLWindowSurfaceColorspaceTestES3 : public EGLSurfaceTest
{};

// Test interaction between GL_EXT_sRGB_write_control and default framebuffer
TEST_P(EGLWindowSurfaceColorspaceTestES3, ToggleSrgbWriteControl)
{
    setWindowVisible(mOSWindow, true);

    initializeDisplay();

    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_gl_colorspace"));

    // Choose an EGLConfig
    constexpr EGLint kConfigAttributes[] = {EGL_RED_SIZE,     8,
                                            EGL_GREEN_SIZE,   8,
                                            EGL_BLUE_SIZE,    8,
                                            EGL_ALPHA_SIZE,   8,
                                            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                                            EGL_NONE};
    EGLint configCount                   = 0;
    EGLConfig config                     = nullptr;
    ANGLE_SKIP_TEST_IF(!eglChooseConfig(mDisplay, kConfigAttributes, &config, 1, &configCount));
    ANGLE_SKIP_TEST_IF(configCount == 0);
    ASSERT_NE(config, nullptr);

    // Create a window surface with sRGB colorspace
    std::vector<EGLint> sutfaceAttribs;
    sutfaceAttribs.push_back(EGL_GL_COLORSPACE);
    sutfaceAttribs.push_back(EGL_GL_COLORSPACE_SRGB);
    sutfaceAttribs.push_back(EGL_NONE);
    initializeSurfaceWithAttribs(config, sutfaceAttribs);
    EXPECT_EGL_SUCCESS();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    // Create a context
    initializeMainContext();
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(mContext, EGL_NO_CONTEXT);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    EXPECT_EGL_SUCCESS();

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_sRGB_write_control"));

    constexpr angle::GLColor uniformColor(13, 54, 133, 255);
    constexpr angle::GLColor linearColor = uniformColor;
    constexpr angle::GLColor srgbColor(64, 127, 191, 255);

    // Bind default framebuffer
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    EXPECT_GL_NO_ERROR();

    // Clear and expect values in sRGB colorspace
    glClearColor(uniformColor[0] / 255.0, uniformColor[1] / 255.0, uniformColor[2] / 255.0,
                 uniformColor[3] / 255.0);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0);
    EXPECT_GL_NO_ERROR();

    // Disable encoding to sRGB colorspace.
    glDisable(GL_FRAMEBUFFER_SRGB_EXT);

    // Clear again but this time expect values in linear colorspace
    glClearColor(uniformColor[0] / 255.0, uniformColor[1] / 255.0, uniformColor[2] / 255.0,
                 uniformColor[3] / 255.0);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, linearColor, 1.0);
    EXPECT_GL_NO_ERROR();
}

// Regression test for a vulkan backend bug where toggling colorspace within a renderpass
// wouldn't refresh the framebuffer
TEST_P(EGLWindowSurfaceColorspaceTestES3, ToggleSrgbWriteControlWithinRenderPass)
{
    setWindowVisible(mOSWindow, true);

    initializeDisplay();

    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_gl_colorspace"));

    // Choose an EGLConfig
    constexpr EGLint kConfigAttributes[] = {EGL_RED_SIZE,     8,
                                            EGL_GREEN_SIZE,   8,
                                            EGL_BLUE_SIZE,    8,
                                            EGL_ALPHA_SIZE,   8,
                                            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                                            EGL_NONE};
    EGLint configCount                   = 0;
    EGLConfig config                     = nullptr;
    ANGLE_SKIP_TEST_IF(!eglChooseConfig(mDisplay, kConfigAttributes, &config, 1, &configCount));
    ANGLE_SKIP_TEST_IF(configCount == 0);
    ASSERT_NE(config, nullptr);

    // Create a window surface with sRGB colorspace
    std::vector<EGLint> sutfaceAttribs;
    sutfaceAttribs.push_back(EGL_GL_COLORSPACE);
    sutfaceAttribs.push_back(EGL_GL_COLORSPACE_SRGB);
    sutfaceAttribs.push_back(EGL_NONE);
    initializeSurfaceWithAttribs(config, sutfaceAttribs);
    EXPECT_EGL_SUCCESS();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    // Create a context
    initializeMainContext();
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(mContext, EGL_NO_CONTEXT);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    EXPECT_EGL_SUCCESS();

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_sRGB_write_control"));

    constexpr angle::GLColor uniformColor(13, 54, 133, 255);
    constexpr angle::GLColor srgbColor(64, 127, 191, 255);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(program);
    GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
    ASSERT_NE(-1, colorLocation);
    Vector4 uniformColorNormalized = uniformColor.toNormalizedVector();
    glUniform4f(colorLocation, uniformColorNormalized.x(), uniformColorNormalized.y(),
                uniformColorNormalized.z(), uniformColorNormalized.w());
    EXPECT_GL_NO_ERROR();

    // Bind default framebuffer
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    EXPECT_GL_NO_ERROR();

    // Disable encoding to sRGB colorspace.
    glDisable(GL_FRAMEBUFFER_SRGB_EXT);

    // Draw
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    // Don't break the renderpass, toggle colorspace and draw again

    // Enable encoding to sRGB colorspace.
    glEnable(GL_FRAMEBUFFER_SRGB_EXT);

    // Draw
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, srgbColor, 1.0);
}

// Test that a PRESERVED surface can be cleared to black (emulated-format clear color).
TEST_P(EGLSurfaceTest, PreserveThenClearToBlack)
{
    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();

    // Initialize an RGBA8 window surface with 4x MSAA
    constexpr EGLint kSurfaceAttributes[] = {
        EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8,
        // Make sure ALPHA is not set, increasing the chance
        // that an RGB format is chosen that is emulated with
        // RGBA.
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_SWAP_BEHAVIOR_PRESERVED_BIT, EGL_NONE};

    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;
    ANGLE_SKIP_TEST_IF(
        !eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ANGLE_SKIP_TEST_IF(configCount == 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // Draw something non-black to the surface.
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);

    // Enable preserve behavior.
    EXPECT_TRUE(
        eglSurfaceAttrib(mDisplay, mWindowSurface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED));
    eglSwapBuffers(mDisplay, mWindowSurface);

    // Clear to black, ensure it's not dropped.
    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
}

// Test that a PRESERVED surface can be blended to.
TEST_P(EGLSurfaceTest, PreserveThenBlend)
{
    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();

    // Initialize an RGBA8 window surface with 4x MSAA
    constexpr EGLint kSurfaceAttributes[] = {
        EGL_RED_SIZE,  8, EGL_GREEN_SIZE,   8,
        EGL_BLUE_SIZE, 8, EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_SWAP_BEHAVIOR_PRESERVED_BIT,
        EGL_NONE};

    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;
    ANGLE_SKIP_TEST_IF(
        !eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ANGLE_SKIP_TEST_IF(configCount == 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // Draw something to the surface.
    ANGLE_GL_PROGRAM(drawBlue, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(drawBlue, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);

    // Enable preserve behavior.
    EXPECT_TRUE(
        eglSurfaceAttrib(mDisplay, mWindowSurface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED));
    eglSwapBuffers(mDisplay, mWindowSurface);

    // Blend something else to the surface.
    ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glBlendFunc(GL_ONE, GL_ONE);
    glEnable(GL_BLEND);
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
}

// Verify switching to preserved swap behavior works if the only update to the surface is clear.
TEST_P(EGLSurfaceTest, ClearThenPreserve)
{
    // Necessary for some platforms (NVIDIA on Linux) if there is no per-frame window size query.
    setWindowVisible(mOSWindow, true);

    initializeDisplay();

    // Initialize an RGBA8 window surface with 4x MSAA
    constexpr EGLint kSurfaceAttributes[] = {
        EGL_RED_SIZE,  8, EGL_GREEN_SIZE,   8,
        EGL_BLUE_SIZE, 8, EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_SWAP_BEHAVIOR_PRESERVED_BIT,
        EGL_NONE};

    EGLint configCount      = 0;
    EGLConfig surfaceConfig = nullptr;
    ANGLE_SKIP_TEST_IF(
        !eglChooseConfig(mDisplay, kSurfaceAttributes, &surfaceConfig, 1, &configCount));
    ANGLE_SKIP_TEST_IF(configCount == 0);
    ASSERT_NE(surfaceConfig, nullptr);

    initializeSurface(surfaceConfig);
    initializeMainContext();
    ASSERT_NE(mWindowSurface, EGL_NO_SURFACE);

    eglMakeCurrent(mDisplay, mWindowSurface, mWindowSurface, mContext);
    ASSERT_EGL_SUCCESS();

    // Clear the surface, but don't flush it.
    glClearColor(0, 0, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    // Enable preserve behavior.
    EXPECT_TRUE(
        eglSurfaceAttrib(mDisplay, mWindowSurface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED));
    eglSwapBuffers(mDisplay, mWindowSurface);

    // Verify that the clear color is visible after swap.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
}

}  // anonymous namespace

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLSingleBufferTest);
ANGLE_INSTANTIATE_TEST(EGLSingleBufferTest,
                       WithNoFixture(ES2_VULKAN()),
                       WithNoFixture(ES3_VULKAN()));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLAndroidAutoRefreshTest);
ANGLE_INSTANTIATE_TEST(EGLAndroidAutoRefreshTest, WithNoFixture(ES3_VULKAN()));

ANGLE_INSTANTIATE_TEST(EGLSurfaceTest,
                       WithNoFixture(ES2_D3D11()),
                       WithNoFixture(ES3_D3D11()),
                       WithNoFixture(ES2_METAL()),
                       WithNoFixture(ES3_METAL()),
                       WithNoFixture(ES2_OPENGL()),
                       WithNoFixture(ES3_OPENGL()),
                       WithNoFixture(ES2_OPENGLES()),
                       WithNoFixture(ES3_OPENGLES()),
                       WithNoFixture(ES2_VULKAN()),
                       WithNoFixture(ES3_VULKAN()),
                       WithNoFixture(ES2_VULKAN_SWIFTSHADER()),
                       WithNoFixture(ES3_VULKAN_SWIFTSHADER()));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLFloatSurfaceTest);
ANGLE_INSTANTIATE_TEST(EGLFloatSurfaceTest,
                       WithNoFixture(ES2_OPENGL()),
                       WithNoFixture(ES3_OPENGL()),
                       WithNoFixture(ES2_VULKAN()),
                       WithNoFixture(ES3_VULKAN()));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLSurfaceTest3);
ANGLE_INSTANTIATE_TEST(EGLSurfaceTest3,
                       WithNoFixture(ES3_D3D11()),
                       WithNoFixture(ES3_OPENGLES()),
                       WithNoFixture(ES3_VULKAN()),
                       WithNoFixture(ES3_VULKAN_SWIFTSHADER()));

#if defined(ANGLE_ENABLE_D3D11)
ANGLE_INSTANTIATE_TEST(EGLSurfaceTestD3D11, WithNoFixture(ES2_D3D11()), WithNoFixture(ES3_D3D11()));
#endif

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLWindowSurfaceColorspaceTestES3);
ANGLE_INSTANTIATE_TEST(EGLWindowSurfaceColorspaceTestES3, WithNoFixture(ES3_VULKAN()));
