//
// Copyright 2021 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.
//
// EGLBufferAgeTest.cpp:
//   EGL extension EGL_EXT_buffer_age
//

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

#include "test_utils/ANGLETest.h"
#include "util/EGLWindow.h"
#include "util/OSWindow.h"

using namespace angle;

class EGLBufferAgeTest : public ANGLETest<>
{
  public:
    EGLBufferAgeTest() : mDisplay(EGL_NO_DISPLAY) {}

    void testSetUp() override
    {
        mOSWindow = OSWindow::New();
        mOSWindow->initialize("EGLBufferAgeTest", 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);
        EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY);
        EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr));
        mMajorVersion       = GetParam().majorVersion;
        mExtensionSupported = IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_buffer_age");
    }

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

        if (mDisplay != EGL_NO_DISPLAY)
        {
            eglTerminate(mDisplay);
            eglReleaseThread();
            mDisplay = EGL_NO_DISPLAY;
        }
        ASSERT_EGL_SUCCESS() << "Error during test TearDown";
    }

    virtual bool chooseConfig(EGLConfig *config) 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,
                                EGL_NONE};

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

    virtual bool chooseConfigWithPreserved(EGLConfig *config) const
    {
        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 | EGL_SWAP_BEHAVIOR_PRESERVED_BIT,
                                EGL_NONE};

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

    bool createContext(EGLConfig config, EGLContext *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) const
    {
        bool result      = false;
        EGLint attribs[] = {EGL_NONE};

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

    EGLint queryAge(EGLSurface surface) const
    {
        EGLint age  = 0;
        bool result = eglQuerySurface(mDisplay, surface, EGL_BUFFER_AGE_EXT, &age);
        EXPECT_TRUE(result);
        return age;
    }

    EGLint queryAgeAttribKHR(EGLSurface surface) const
    {
        EGLAttribKHR age = 0;
        bool result      = eglQuerySurface64KHR(mDisplay, surface, EGL_BUFFER_AGE_EXT, &age);
        EXPECT_TRUE(result);
        return age;
    }

    OSWindow *mOSWindow      = nullptr;
    EGLDisplay mDisplay      = EGL_NO_DISPLAY;
    EGLint mMajorVersion     = 0;
    const EGLint kWidth      = 64;
    const EGLint kHeight     = 64;
    bool mExtensionSupported = false;
};

class EGLBufferAgeTest_MSAA : public EGLBufferAgeTest
{
  public:
    EGLBufferAgeTest_MSAA() : EGLBufferAgeTest() {}

    bool chooseConfig(EGLConfig *config) const override
    {
        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,
                                8,
                                EGL_RENDERABLE_TYPE,
                                clientVersion,
                                EGL_SAMPLE_BUFFERS,
                                1,
                                EGL_SAMPLES,
                                4,
                                EGL_SURFACE_TYPE,
                                EGL_WINDOW_BIT,
                                EGL_NONE};

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

class EGLBufferAgeTest_MSAA_DS : public EGLBufferAgeTest
{
  public:
    EGLBufferAgeTest_MSAA_DS() : EGLBufferAgeTest() {}

    bool chooseConfig(EGLConfig *config) const override
    {
        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,
                                8,
                                EGL_DEPTH_SIZE,
                                8,
                                EGL_STENCIL_SIZE,
                                8,
                                EGL_RENDERABLE_TYPE,
                                clientVersion,
                                EGL_SAMPLE_BUFFERS,
                                1,
                                EGL_SAMPLES,
                                4,
                                EGL_SURFACE_TYPE,
                                EGL_WINDOW_BIT,
                                EGL_NONE};

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

// Query for buffer age
TEST_P(EGLBufferAgeTest, QueryBufferAge)
{
    ANGLE_SKIP_TEST_IF(!mExtensionSupported);

    const bool lockSurface3ExtSupported =
        IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_lock_surface3");

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));

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

    EGLSurface surface = EGL_NO_SURFACE;

    EXPECT_TRUE(createWindowSurface(config, mOSWindow->getNativeWindow(), &surface));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

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

    glClearColor(1.0, 0.0, 0.0, 1.0);

    const uint32_t loopcount = 15;
    EGLint expectedAge       = 0;
    for (uint32_t i = 0; i < loopcount; i++)
    {
        // Alternate between eglQuerySurface and eglQuerySurface64KHR
        EGLint age = -1;
        if (i % 2 == 0 || !lockSurface3ExtSupported)
        {
            age = queryAge(surface);
        }
        else
        {
            age = static_cast<EGLint>(queryAgeAttribKHR(surface));
        }

        // Should start with zero age and then flip to buffer count - which we don't know.
        if ((expectedAge == 0) && (age > 0))
        {
            expectedAge = age;
        }
        EXPECT_EQ(age, expectedAge);

        glClear(GL_COLOR_BUFFER_BIT);
        ASSERT_GL_NO_ERROR() << "glClear failed";
        eglSwapBuffers(mDisplay, surface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    }

    EXPECT_GT(expectedAge, 0);

    EXPECT_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;
}

// Query for buffer age after several loops of swapping buffers
TEST_P(EGLBufferAgeTest, QueryBufferAgeAfterLoop)
{
    ANGLE_SKIP_TEST_IF(!mExtensionSupported);

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));

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

    EGLSurface surface = EGL_NO_SURFACE;

    EXPECT_TRUE(createWindowSurface(config, mOSWindow->getNativeWindow(), &surface));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

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

    glClearColor(1.0, 0.0, 0.0, 1.0);

    const uint32_t loopcount = 5;
    for (uint32_t i = 0; i < loopcount; i++)
    {
        glClear(GL_COLOR_BUFFER_BIT);
        ASSERT_GL_NO_ERROR() << "glClear failed";
        eglSwapBuffers(mDisplay, surface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    }

    // This query age should not reset age
    EXPECT_GT(queryAge(surface), 0);

    EXPECT_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;
}

// Verify contents of buffer are as expected
TEST_P(EGLBufferAgeTest, VerifyContents)
{
    ANGLE_SKIP_TEST_IF(!mExtensionSupported);

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));

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

    EGLSurface surface = EGL_NO_SURFACE;

    EXPECT_TRUE(createWindowSurface(config, mOSWindow->getNativeWindow(), &surface));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

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

    const angle::GLColor kLightGray(191, 191, 191, 255);  // 0.75
    const angle::GLColor kDarkGray(64, 64, 64, 255);      // 0.25
    const angle::GLColor kColorSet[] = {
        GLColor::blue,  GLColor::cyan,   kDarkGray,      GLColor::green,   GLColor::red,
        GLColor::white, GLColor::yellow, GLColor::black, GLColor::magenta, kLightGray,
        GLColor::black,  // Extra loops until color cycled through
        GLColor::black, GLColor::black,  GLColor::black, GLColor::black};

    EGLint age                   = 0;
    angle::GLColor expectedColor = GLColor::black;
    int loopCount                = (sizeof(kColorSet) / sizeof(kColorSet[0]));
    for (int i = 0; i < loopCount; i++)
    {
        age = queryAge(surface);
        if (age > 0)
        {
            // Check that color/content is what we expect
            expectedColor = ANGLE_UNSAFE_TODO(kColorSet[i - age]);
            EXPECT_PIXEL_COLOR_EQ(1, 1, expectedColor);
        }

        float red   = ANGLE_UNSAFE_TODO(kColorSet[i]).R / 255.0;
        float green = ANGLE_UNSAFE_TODO(kColorSet[i]).G / 255.0;
        float blue  = ANGLE_UNSAFE_TODO(kColorSet[i]).B / 255.0;
        float alpha = ANGLE_UNSAFE_TODO(kColorSet[i]).A / 255.0;

        glClearColor(red, green, blue, alpha);
        glClear(GL_COLOR_BUFFER_BIT);
        ASSERT_GL_NO_ERROR() << "glClear failed";
        eglSwapBuffers(mDisplay, surface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    }

    EXPECT_GT(age, 0);

    EXPECT_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;
}

// Verify that buffer age query is correct if the preserved behavior is set after many swaps.
TEST_P(EGLBufferAgeTest, VerifyContentsAfterSwapBehaviorSwitch)
{
    ANGLE_SKIP_TEST_IF(!mExtensionSupported);

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfigWithPreserved(&config));

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

    EGLSurface surface = EGL_NO_SURFACE;

    EXPECT_TRUE(createWindowSurface(config, mOSWindow->getNativeWindow(), &surface));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

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

    EGLint defaultBehavior = EGL_BUFFER_DESTROYED;
    EXPECT_EGL_TRUE(eglQuerySurface(mDisplay, surface, EGL_SWAP_BEHAVIOR, &defaultBehavior));

    // Issue a number of swaps without querying the buffer age.
    glClearColor(0, 0, 0, 0);
    glClearDepthf(0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    GLuint program = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
    glUseProgram(program);

    for (uint32_t i = 0; i < 5; ++i)
    {
        glUniform4fv(colorLocation, 1, GLColor(i * 50, 0, 0, 255).toNormalizedVector().data());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        eglSwapBuffers(mDisplay, surface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    }

    // Set behavior to PRESERVED.  This is done after swap, so contents of previous frame aren't
    // expected to have been preserved.
    EXPECT_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED));

    // Query the age.  If the default behavior is PRESERVED, age should be 1.  Otherwise age could
    // be anything.
    EGLint age = 0;
    age        = queryAge(surface);
    if (defaultBehavior == EGL_BUFFER_DESTROYED)
    {
        EXPECT_GE(age, 0);
    }
    else
    {
        EXPECT_EQ(age, 1);
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor(200, 0, 0, 255));
    }

    // Issue a few other swaps, the age should be 1 from here on out because behavior is PRESERVED.
    for (uint32_t i = 0; i < 6; i++)
    {
        glUniform4fv(colorLocation, 1, GLColor(0, i * 40, 0, 255).toNormalizedVector().data());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        eglSwapBuffers(mDisplay, surface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";

        age = queryAge(surface);
        EXPECT_EQ(age, 1);
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor(0, i * 40, 0, 255));
    }

    // Set behavior to DESTROYED.  Content must be rendered correctly, and age query should work as
    // expected.
    EXPECT_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED));

    const angle::GLColor kLightGray(191, 191, 191, 255);  // 0.75
    const angle::GLColor kDarkGray(64, 64, 64, 255);      // 0.25
    const angle::GLColor kColorSet[] = {
        GLColor::blue,  GLColor::cyan,   kDarkGray,      GLColor::green,   GLColor::red,
        GLColor::white, GLColor::yellow, GLColor::black, GLColor::magenta, kLightGray,
        GLColor::black,  // Extra loops until color cycled through
        GLColor::black, GLColor::black,  GLColor::black, GLColor::black};

    angle::GLColor expectedColor = GLColor::black;
    int loopCount                = (sizeof(kColorSet) / sizeof(kColorSet[0]));
    for (int i = 0; i < loopCount; i++)
    {
        age = queryAge(surface);
        if (age > 0)
        {
            // Check that color/content is what we expect.  The age could refer to rendering done in
            // the previous loops.
            if (age <= i)
            {
                expectedColor = ANGLE_UNSAFE_TODO(kColorSet[i - age]);
            }
            else if (age <= i + 6)
            {
                expectedColor = GLColor(0, (6 - (age - i)) * 40, 0, 255);
            }
            else
            {
                ASSERT_LE(age, i + 11);
                expectedColor = GLColor((11 - (age - i)) * 50, 0, 0, 255);
            }
            EXPECT_PIXEL_COLOR_EQ(1, 1, expectedColor);
        }

        glUniform4fv(colorLocation, 1, ANGLE_UNSAFE_TODO(kColorSet[i]).toNormalizedVector().data());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        ANGLE_UNSAFE_TODO(EXPECT_PIXEL_COLOR_EQ(0, 0, kColorSet[i]));
        eglSwapBuffers(mDisplay, surface);
    }

    EXPECT_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;
}

// Verify contents of buffer are as expected for a multisample image
TEST_P(EGLBufferAgeTest_MSAA, VerifyContentsForMultisampled)
{
    ANGLE_SKIP_TEST_IF(!mExtensionSupported);

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));

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

    EGLSurface surface = EGL_NO_SURFACE;

    EXPECT_TRUE(createWindowSurface(config, mOSWindow->getNativeWindow(), &surface));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

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

    std::vector<angle::GLColor> kColorSet;
    for (uint32_t i = 0; i < 16; i++)
    {
        kColorSet.emplace_back(i * 10, 0, 0, 255);
    }

    // Set up
    glClearColor(0, 0, 0, 0);
    glClearDepthf(0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    EGLint age     = 0;
    GLuint program = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());

    for (size_t i = 0; i < kColorSet.size(); i++)
    {
        age = queryAge(surface);
        if (age > 0)
        {
            // Check that color/content is what we expect
            angle::GLColor expectedColor = kColorSet[i - age];
            EXPECT_PIXEL_COLOR_EQ(1, 1, expectedColor);
        }
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glUseProgram(program);
        glUniform4fv(colorLocation, 1, kColorSet[i].toNormalizedVector().data());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

        eglSwapBuffers(mDisplay, surface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    }

    EXPECT_GE(age, 0);

    EXPECT_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;
}

// Verify that buffer age query is correct if the preserved behavior is set after many swaps.
TEST_P(EGLBufferAgeTest_MSAA, VerifyContentsAfterSwapBehaviorSwitch)
{
    ANGLE_SKIP_TEST_IF(!mExtensionSupported);

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));

    {
        // Nothing to test if the PRESERVED behavior is not supported.
        EGLint surfaceType;
        EXPECT_EGL_TRUE(eglGetConfigAttrib(mDisplay, config, EGL_SURFACE_TYPE, &surfaceType));
        ANGLE_SKIP_TEST_IF((surfaceType & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) == 0);
    }

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

    EGLSurface surface = EGL_NO_SURFACE;

    EXPECT_TRUE(createWindowSurface(config, mOSWindow->getNativeWindow(), &surface));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

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

    EGLint defaultBehavior = EGL_BUFFER_DESTROYED;
    EXPECT_EGL_TRUE(eglQuerySurface(mDisplay, surface, EGL_SWAP_BEHAVIOR, &defaultBehavior));

    // Issue a number of swaps without querying the buffer age.
    glClearColor(0, 0, 0, 0);
    glClearDepthf(0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    GLuint program = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
    glUseProgram(program);

    for (uint32_t i = 0; i < 5; ++i)
    {
        glUniform4fv(colorLocation, 1, GLColor(i * 50, 0, 0, 255).toNormalizedVector().data());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        eglSwapBuffers(mDisplay, surface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    }

    // Set behavior to PRESERVED.  This is done after swap, so contents of previous frame aren't
    // expected to have been preserved.
    EXPECT_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED));

    // Query the age.  If the default behavior is PRESERVED, age should be 1.  Otherwise age could
    // be anything.
    EGLint age = 0;
    age        = queryAge(surface);
    if (defaultBehavior == EGL_BUFFER_DESTROYED)
    {
        EXPECT_GE(age, 0);
    }
    else
    {
        EXPECT_EQ(age, 1);
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor(200, 0, 0, 255));
    }

    // Issue a few other swaps, the age should be 1 from here on out because behavior is PRESERVED.
    for (uint32_t i = 0; i < 6; i++)
    {
        glUniform4fv(colorLocation, 1, GLColor(0, i * 40, 0, 255).toNormalizedVector().data());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        eglSwapBuffers(mDisplay, surface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";

        age = queryAge(surface);
        EXPECT_EQ(age, 1);
        EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor(0, i * 40, 0, 255));
    }

    EXPECT_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;
}

// Verify contents of buffer are as expected for a multisample image with depth/stencil enabled
TEST_P(EGLBufferAgeTest_MSAA_DS, VerifyContentsForMultisampledWithDepthStencil)
{
    ANGLE_SKIP_TEST_IF(!mExtensionSupported);

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));

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

    EGLSurface surface = EGL_NO_SURFACE;

    EXPECT_TRUE(createWindowSurface(config, mOSWindow->getNativeWindow(), &surface));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

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

    std::vector<angle::GLColor> kColorSet;
    for (uint32_t i = 0; i < 16; i++)
    {
        kColorSet.emplace_back(i * 10, 0, 0, 255);
    }

    // Set up
    glClearColor(0, 0, 0, 0);
    glClearDepthf(0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    EGLint age     = 0;
    GLuint program = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());

    for (size_t i = 0; i < kColorSet.size(); i++)
    {
        age = queryAge(surface);
        if (age > 0)
        {
            // Check that color/content is what we expect
            angle::GLColor expectedColor = kColorSet[i - age];
            EXPECT_PIXEL_COLOR_EQ(1, 1, expectedColor);
        }
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glUseProgram(program);
        glUniform4fv(colorLocation, 1, kColorSet[i].toNormalizedVector().data());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

        eglSwapBuffers(mDisplay, surface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    }

    EXPECT_GE(age, 0);

    EXPECT_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;
}

// Verify EGL_BAD_SURFACE when not current
TEST_P(EGLBufferAgeTest, UncurrentContextBadSurface)
{
    ANGLE_SKIP_TEST_IF(!mExtensionSupported);

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));

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

    EGLSurface surface = EGL_NO_SURFACE;

    EXPECT_TRUE(createWindowSurface(config, mOSWindow->getNativeWindow(), &surface));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

    // No current context

    EGLint value = 0;
    EXPECT_EGL_FALSE(eglQuerySurface(mDisplay, surface, EGL_BUFFER_AGE_EXT, &value));
    EXPECT_EGL_ERROR(EGL_BAD_SURFACE);

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

    // Surface current to another context
    EXPECT_TRUE(eglMakeCurrent(mDisplay, surface, surface, otherContext));
    // Make context the active context
    EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context));

    value = 0;
    EXPECT_EGL_FALSE(eglQuerySurface(mDisplay, surface, EGL_BUFFER_AGE_EXT, &value));
    EXPECT_EGL_ERROR(EGL_BAD_SURFACE);

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

    eglDestroyContext(mDisplay, otherContext);
    otherContext = EGL_NO_CONTEXT;

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

// Test if the content in damage region is defined
TEST_P(EGLBufferAgeTest, ValidateDamageRegion)
{
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_partial_update"));

    EGLConfig config = EGL_NO_CONFIG_KHR;
    EXPECT_TRUE(chooseConfig(&config));

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

    EGLSurface surface = EGL_NO_SURFACE;

    EXPECT_TRUE(createWindowSurface(config, mOSWindow->getNativeWindow(), &surface));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";
    EXPECT_TRUE(eglMakeCurrent(mDisplay, surface, surface, context));

    EGLint age                               = 0;
    EGLint rect[4]                           = {0, 0, 1, 1};
    std::vector<GLColor> colors = {GLColor::white,  GLColor::red,  GLColor::green,  GLColor::blue,
                                   GLColor::yellow, GLColor::cyan, GLColor::magenta};

    glDisable(GL_SCISSOR_TEST);
    for (auto color : colors)
    {
        const angle::Vector4 clearColor = color.toNormalizedVector();
        glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
        glClear(GL_COLOR_BUFFER_BIT);
        EXPECT_EGL_TRUE(eglSwapBuffers(mDisplay, surface));
        EXPECT_EGL_SUCCESS();
        ASSERT_GL_NO_ERROR();
    }

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

    if (age > 0)
    {
        eglSetDamageRegionKHR(mDisplay, surface, rect, 1);
        EXPECT_EGL_SUCCESS();

        glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
        glEnable(GL_SCISSOR_TEST);
        glScissor(0, 0, 1, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        glDisable(GL_SCISSOR_TEST);
        ASSERT_GL_NO_ERROR();

        const GLColor &expectColor = colors[colors.size() - age];
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
        EXPECT_PIXEL_COLOR_EQ(1, 1, expectColor);
    }

    eglDestroySurface(mDisplay, surface);
    surface = EGL_NO_SURFACE;

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

// Expect age always == 1 when EGL_BUFFER_PRESERVED is chosen
TEST_P(EGLBufferAgeTest, BufferPreserved)
{
    ANGLE_SKIP_TEST_IF(!mExtensionSupported);

    EGLConfig config = EGL_NO_CONFIG_KHR;
    ANGLE_SKIP_TEST_IF(!chooseConfigWithPreserved(&config));

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

    EGLSurface surface = EGL_NO_SURFACE;

    EXPECT_TRUE(createWindowSurface(config, mOSWindow->getNativeWindow(), &surface));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

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

    // Set behavior to PRESERVED, if not default.
    EXPECT_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED));

    glClearColor(1.0, 0.0, 0.0, 1.0);

    const uint32_t loopcount = 10;
    EGLint expectedAge       = 0;
    for (uint32_t i = 0; i < loopcount; i++)
    {
        EGLint age = queryAge(surface);
        EXPECT_EQ(age, expectedAge);
        // After the first frame, the age must always be 1.
        expectedAge = 1;

        glClear(GL_COLOR_BUFFER_BIT);
        ASSERT_GL_NO_ERROR() << "glClear failed";
        eglSwapBuffers(mDisplay, surface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    }

    EXPECT_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;
}

// Expect age always == 0 when EGL_SINGLE_BUFFER is chosen
TEST_P(EGLBufferAgeTest, SingleBuffer)
{
    ANGLE_SKIP_TEST_IF(!mExtensionSupported);
    ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_mutable_render_buffer"));

    EGLConfig config     = EGL_NO_CONFIG_KHR;
    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 | EGL_MUTABLE_RENDER_BUFFER_BIT_KHR,
                            EGL_NONE};

    EXPECT_EGL_TRUE(eglChooseConfig(mDisplay, attribs, &config, 1, &count));
    // Skip if no configs, this indicates EGL_SINGLE_BUFFER is not supported.
    ANGLE_SKIP_TEST_IF(count == 0);

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

    EGLSurface surface = EGL_NO_SURFACE;

    EXPECT_TRUE(createWindowSurface(config, mOSWindow->getNativeWindow(), &surface));
    ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";

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

    // Set render buffer to EGL_SINGLE_BUFFER
    EXPECT_EGL_TRUE(eglSurfaceAttrib(mDisplay, surface, EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));

    glClearColor(1.0, 0.0, 0.0, 1.0);

    // Expect age = 0 before first eglSwapBuffers() call
    EGLint age = queryAge(surface);
    EXPECT_EQ(age, 0);
    eglSwapBuffers(mDisplay, surface);
    ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";

    const uint32_t loopcount = 10;
    EGLint expectedAge       = 0;
    for (uint32_t i = 0; i < loopcount; i++)
    {
        age = queryAge(surface);
        EXPECT_EQ(age, expectedAge);

        glClear(GL_COLOR_BUFFER_BIT);
        ASSERT_GL_NO_ERROR() << "glClear failed";
        eglSwapBuffers(mDisplay, surface);
        ASSERT_EGL_SUCCESS() << "eglSwapBuffers failed.";
    }

    EXPECT_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_INSTANTIATE_TEST(EGLBufferAgeTest,
                       WithNoFixture(ES2_METAL()),
                       WithNoFixture(ES3_METAL()),
                       WithNoFixture(ES2_OPENGLES()),
                       WithNoFixture(ES3_OPENGLES()),
                       WithNoFixture(ES2_OPENGL()),
                       WithNoFixture(ES3_OPENGL()),
                       WithNoFixture(ES2_VULKAN()),
                       WithNoFixture(ES3_VULKAN()));
ANGLE_INSTANTIATE_TEST(EGLBufferAgeTest_MSAA,
                       WithNoFixture(ES2_METAL()),
                       WithNoFixture(ES3_METAL()),
                       WithNoFixture(ES2_OPENGLES()),
                       WithNoFixture(ES3_OPENGLES()),
                       WithNoFixture(ES2_OPENGL()),
                       WithNoFixture(ES3_OPENGL()),
                       WithNoFixture(ES2_VULKAN()),
                       WithNoFixture(ES3_VULKAN()));
ANGLE_INSTANTIATE_TEST(EGLBufferAgeTest_MSAA_DS,
                       WithNoFixture(ES2_METAL()),
                       WithNoFixture(ES3_METAL()),
                       WithNoFixture(ES2_OPENGLES()),
                       WithNoFixture(ES3_OPENGLES()),
                       WithNoFixture(ES2_OPENGL()),
                       WithNoFixture(ES3_OPENGL()),
                       WithNoFixture(ES2_VULKAN()),
                       WithNoFixture(ES3_VULKAN()));
