//
// 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.
//
// ANGLETest:
//   Implementation of common ANGLE testing fixture.
//

#include "ANGLETest.h"

#include "common/platform.h"
#include "gpu_info_util/SystemInfo.h"
#include "util/EGLWindow.h"
#include "util/OSWindow.h"

#if defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
#    include "util/windows/WGLWindow.h"
#endif  // defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)

#if defined(ANGLE_PLATFORM_WINDOWS)
#    include <VersionHelpers.h>
#endif  // defined(ANGLE_PLATFORM_WINDOWS)

namespace angle
{

const GLColorRGB GLColorRGB::black(0u, 0u, 0u);
const GLColorRGB GLColorRGB::blue(0u, 0u, 255u);
const GLColorRGB GLColorRGB::green(0u, 255u, 0u);
const GLColorRGB GLColorRGB::red(255u, 0u, 0u);
const GLColorRGB GLColorRGB::yellow(255u, 255u, 0);

const GLColor GLColor::black            = GLColor(0u, 0u, 0u, 255u);
const GLColor GLColor::blue             = GLColor(0u, 0u, 255u, 255u);
const GLColor GLColor::cyan             = GLColor(0u, 255u, 255u, 255u);
const GLColor GLColor::green            = GLColor(0u, 255u, 0u, 255u);
const GLColor GLColor::red              = GLColor(255u, 0u, 0u, 255u);
const GLColor GLColor::transparentBlack = GLColor(0u, 0u, 0u, 0u);
const GLColor GLColor::white            = GLColor(255u, 255u, 255u, 255u);
const GLColor GLColor::yellow           = GLColor(255u, 255u, 0, 255u);
const GLColor GLColor::magenta          = GLColor(255u, 0u, 255u, 255u);

namespace
{
float ColorNorm(GLubyte channelValue)
{
    return static_cast<float>(channelValue) / 255.0f;
}

GLubyte ColorDenorm(float colorValue)
{
    return static_cast<GLubyte>(colorValue * 255.0f);
}

void TestPlatform_logError(PlatformMethods *platform, const char *errorMessage)
{
    auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
    if (testPlatformContext->ignoreMessages)
        return;

    FAIL() << errorMessage;
}

void TestPlatform_logWarning(PlatformMethods *platform, const char *warningMessage)
{
    auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
    if (testPlatformContext->ignoreMessages)
        return;

    if (testPlatformContext->warningsAsErrors)
    {
        FAIL() << warningMessage;
    }
    else
    {
        std::cerr << "Warning: " << warningMessage << std::endl;
    }
}

void TestPlatform_logInfo(PlatformMethods *platform, const char *infoMessage)
{
    auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
    if (testPlatformContext->ignoreMessages)
        return;

    WriteDebugMessage("%s\n", infoMessage);
}

void TestPlatform_overrideWorkaroundsD3D(PlatformMethods *platform, WorkaroundsD3D *workaroundsD3D)
{
    auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
    if (testPlatformContext->currentTest)
    {
        testPlatformContext->currentTest->overrideWorkaroundsD3D(workaroundsD3D);
    }
}

void TestPlatform_overrideFeaturesVk(PlatformMethods *platform, FeaturesVk *workaroundsVulkan)
{
    auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
    if (testPlatformContext->currentTest)
    {
        testPlatformContext->currentTest->overrideFeaturesVk(workaroundsVulkan);
    }
}

const std::array<Vector3, 6> kQuadVertices = {{
    Vector3(-1.0f, 1.0f, 0.5f),
    Vector3(-1.0f, -1.0f, 0.5f),
    Vector3(1.0f, -1.0f, 0.5f),
    Vector3(-1.0f, 1.0f, 0.5f),
    Vector3(1.0f, -1.0f, 0.5f),
    Vector3(1.0f, 1.0f, 0.5f),
}};

const std::array<Vector3, 4> kIndexedQuadVertices = {{
    Vector3(-1.0f, 1.0f, 0.5f),
    Vector3(-1.0f, -1.0f, 0.5f),
    Vector3(1.0f, -1.0f, 0.5f),
    Vector3(1.0f, 1.0f, 0.5f),
}};

constexpr std::array<GLushort, 6> kIndexedQuadIndices = {{0, 1, 2, 0, 2, 3}};

const char *GetColorName(GLColor color)
{
    if (color == GLColor::red)
    {
        return "Red";
    }

    if (color == GLColor::green)
    {
        return "Green";
    }

    if (color == GLColor::blue)
    {
        return "Blue";
    }

    if (color == GLColor::white)
    {
        return "White";
    }

    if (color == GLColor::black)
    {
        return "Black";
    }

    if (color == GLColor::transparentBlack)
    {
        return "Transparent Black";
    }

    if (color == GLColor::yellow)
    {
        return "Yellow";
    }

    if (color == GLColor::magenta)
    {
        return "Magenta";
    }

    if (color == GLColor::cyan)
    {
        return "Cyan";
    }

    return nullptr;
}
}  // anonymous namespace

GLColorRGB::GLColorRGB() : R(0), G(0), B(0) {}

GLColorRGB::GLColorRGB(GLubyte r, GLubyte g, GLubyte b) : R(r), G(g), B(b) {}

GLColorRGB::GLColorRGB(const Vector3 &floatColor)
    : R(ColorDenorm(floatColor.x())), G(ColorDenorm(floatColor.y())), B(ColorDenorm(floatColor.z()))
{}

GLColor::GLColor() : R(0), G(0), B(0), A(0) {}

GLColor::GLColor(GLubyte r, GLubyte g, GLubyte b, GLubyte a) : R(r), G(g), B(b), A(a) {}

GLColor::GLColor(const Vector4 &floatColor)
    : R(ColorDenorm(floatColor.x())),
      G(ColorDenorm(floatColor.y())),
      B(ColorDenorm(floatColor.z())),
      A(ColorDenorm(floatColor.w()))
{}

GLColor::GLColor(GLuint colorValue) : R(0), G(0), B(0), A(0)
{
    memcpy(&R, &colorValue, sizeof(GLuint));
}

testing::AssertionResult GLColor::ExpectNear(const GLColor &expected, const GLColor &err) const
{
    testing::AssertionResult result(
        abs(int(expected.R) - this->R) <= err.R && abs(int(expected.G) - this->G) <= err.G &&
        abs(int(expected.B) - this->B) <= err.B && abs(int(expected.A) - this->A) <= err.A);
    if (!bool(result))
    {
        result << "Expected " << expected << "+/-" << err << ", was " << *this;
    }
    return result;
}

void CreatePixelCenterWindowCoords(const std::vector<Vector2> &pixelPoints,
                                   int windowWidth,
                                   int windowHeight,
                                   std::vector<Vector3> *outVertices)
{
    for (Vector2 pixelPoint : pixelPoints)
    {
        outVertices->emplace_back(Vector3((pixelPoint[0] + 0.5f) * 2.0f / windowWidth - 1.0f,
                                          (pixelPoint[1] + 0.5f) * 2.0f / windowHeight - 1.0f,
                                          0.0f));
    }
}

Vector4 GLColor::toNormalizedVector() const
{
    return Vector4(ColorNorm(R), ColorNorm(G), ColorNorm(B), ColorNorm(A));
}

GLColor ReadColor(GLint x, GLint y)
{
    GLColor actual;
    glReadPixels((x), (y), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &actual.R);
    EXPECT_GL_NO_ERROR();
    return actual;
}

bool operator==(const GLColor &a, const GLColor &b)
{
    return a.R == b.R && a.G == b.G && a.B == b.B && a.A == b.A;
}

bool operator!=(const GLColor &a, const GLColor &b)
{
    return !(a == b);
}

std::ostream &operator<<(std::ostream &ostream, const GLColor &color)
{
    const char *colorName = GetColorName(color);
    if (colorName)
    {
        return ostream << colorName;
    }

    ostream << "(" << static_cast<unsigned int>(color.R) << ", "
            << static_cast<unsigned int>(color.G) << ", " << static_cast<unsigned int>(color.B)
            << ", " << static_cast<unsigned int>(color.A) << ")";
    return ostream;
}

bool operator==(const GLColor32F &a, const GLColor32F &b)
{
    return a.R == b.R && a.G == b.G && a.B == b.B && a.A == b.A;
}

std::ostream &operator<<(std::ostream &ostream, const GLColor32F &color)
{
    ostream << "(" << color.R << ", " << color.G << ", " << color.B << ", " << color.A << ")";
    return ostream;
}

GLColor32F ReadColor32F(GLint x, GLint y)
{
    GLColor32F actual;
    glReadPixels((x), (y), 1, 1, GL_RGBA, GL_FLOAT, &actual.R);
    EXPECT_GL_NO_ERROR();
    return actual;
}

}  // namespace angle

// static
std::array<angle::Vector3, 6> ANGLETestBase::GetQuadVertices()
{
    return angle::kQuadVertices;
}

// static
std::array<GLushort, 6> ANGLETestBase::GetQuadIndices()
{
    return angle::kIndexedQuadIndices;
}

// static
std::array<angle::Vector3, 4> ANGLETestBase::GetIndexedQuadVertices()
{
    return angle::kIndexedQuadVertices;
}

ANGLETestBase::ANGLETestBase(const angle::PlatformParameters &params)
    : mEGLWindow(nullptr),
      mWGLWindow(nullptr),
      mWidth(16),
      mHeight(16),
      mIgnoreD3D11SDKLayersWarnings(false),
      mQuadVertexBuffer(0),
      mQuadIndexBuffer(0),
      m2DTexturedQuadProgram(0),
      m3DTexturedQuadProgram(0),
      mDeferContextInit(false)
{
    switch (params.driver)
    {
        case angle::GLESDriverType::AngleEGL:
        {
            mEGLWindow =
                EGLWindow::New(params.majorVersion, params.minorVersion, params.eglParameters);

            // Default debug layers to enabled in tests.
            mEGLWindow->setDebugLayersEnabled(true);

            // Workaround for NVIDIA not being able to share OpenGL and Vulkan contexts.
            // Workaround if any of the GPUs is Nvidia, since we can't detect current GPU.
            EGLint renderer = params.getRenderer();
            bool needsWindowSwap =
                hasNvidiaGPU() && mLastRendererType.valid() &&
                ((renderer != EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE) !=
                 (mLastRendererType.value() != EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE));

            if (needsWindowSwap)
            {
                DestroyTestWindow();
                if (!InitTestWindow())
                {
                    std::cerr << "Failed to create ANGLE test window.";
                }
            }

            mLastRendererType = renderer;
            break;
        }

        case angle::GLESDriverType::SystemEGL:
        {
            std::cerr << "Unsupported driver." << std::endl;
            break;
        }

        case angle::GLESDriverType::SystemWGL:
        {
#if defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
            mWGLWindow = WGLWindow::New(params.majorVersion, params.minorVersion);
#else
            std::cerr << "Unsupported driver." << std::endl;
#endif  // defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
            break;
        }
    }
}

ANGLETestBase::~ANGLETestBase()
{
    if (mQuadVertexBuffer)
    {
        glDeleteBuffers(1, &mQuadVertexBuffer);
    }
    if (mQuadIndexBuffer)
    {
        glDeleteBuffers(1, &mQuadIndexBuffer);
    }
    if (m2DTexturedQuadProgram)
    {
        glDeleteProgram(m2DTexturedQuadProgram);
    }
    if (m3DTexturedQuadProgram)
    {
        glDeleteProgram(m3DTexturedQuadProgram);
    }
    EGLWindow::Delete(&mEGLWindow);

#if defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
    WGLWindow::Delete(&mWGLWindow);
#endif  // defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
}

void ANGLETestBase::ANGLETestSetUp()
{
    mPlatformContext.ignoreMessages   = false;
    mPlatformContext.warningsAsErrors = false;
    mPlatformContext.currentTest      = this;

    // Resize the window before creating the context so that the first make current
    // sets the viewport and scissor box to the right size.
    bool needSwap = false;
    if (mOSWindow->getWidth() != mWidth || mOSWindow->getHeight() != mHeight)
    {
        if (!mOSWindow->resize(mWidth, mHeight))
        {
            FAIL() << "Failed to resize ANGLE test window.";
        }
        needSwap = true;
    }

    if (mWGLWindow)
    {
#if defined(ANGLE_PLATFORM_WINDOWS) && defined(ANGLE_USE_UTIL_LOADER)
        if (!mWGLWindow->initializeGL(mOSWindow, ANGLETestEnvironment::GetWGLLibrary()))
        {
            std::cerr << "WGL init failed.. trying again with new OSWindow." << std::endl;

            // Retry once with a fresh OSWindow. This is necessary to work around a bug in the
            // NVIDIA WGL implementation. It seems sometimes the pixel format gets stuck. Using
            // a new windows seems to allow us to set the new pixel format correctly.
            DestroyTestWindow();
            if (!InitTestWindow())
            {
                FAIL() << "Failed to create ANGLE test window.";
            }

            if (!mWGLWindow->initializeGL(mOSWindow, ANGLETestEnvironment::GetWGLLibrary()))
            {
                FAIL() << "WGL init failed.";
            }
        }
#else
        FAIL() << "Unsupported driver.";
#endif  // defined(ANGLE_PLATFORM_WINDOWS) && defined(ANGLE_USE_UTIL_LOADER)
    }
    else
    {
        mPlatformMethods.overrideWorkaroundsD3D = angle::TestPlatform_overrideWorkaroundsD3D;
        mPlatformMethods.overrideFeaturesVk     = angle::TestPlatform_overrideFeaturesVk;
        mPlatformMethods.logError               = angle::TestPlatform_logError;
        mPlatformMethods.logWarning             = angle::TestPlatform_logWarning;
        mPlatformMethods.logInfo                = angle::TestPlatform_logInfo;
        mPlatformMethods.context                = &mPlatformContext;
        mEGLWindow->setPlatformMethods(&mPlatformMethods);

        if (!mEGLWindow->initializeDisplayAndSurface(mOSWindow,
                                                     ANGLETestEnvironment::GetEGLLibrary()))
        {
            FAIL() << "egl display or surface init failed.";
        }

        if (!mDeferContextInit && !mEGLWindow->initializeContext())
        {
            FAIL() << "GL Context init failed.";
        }
    }

    if (needSwap)
    {
        // Swap the buffers so that the default framebuffer picks up the resize
        // which will allow follow-up test code to assume the framebuffer covers
        // the whole window.
        swapBuffers();
    }

    // This Viewport command is not strictly necessary but we add it so that programs
    // taking OpenGL traces can guess the size of the default framebuffer and show it
    // in their UIs
    glViewport(0, 0, mWidth, mHeight);

    const auto &info = testing::UnitTest::GetInstance()->current_test_info();
    angle::WriteDebugMessage("Entering %s.%s\n", info->test_case_name(), info->name());
}

void ANGLETestBase::ANGLETestTearDown()
{
    if (mEGLWindow)
    {
        mEGLWindow->setPlatformMethods(nullptr);
        checkD3D11SDKLayersMessages();
    }

    mPlatformContext.currentTest = nullptr;

    const auto &info = testing::UnitTest::GetInstance()->current_test_info();
    angle::WriteDebugMessage("Exiting %s.%s\n", info->test_case_name(), info->name());

    swapBuffers();

    mOSWindow->messageLoop();

    getGLWindow()->destroyGL();

    // Check for quit message
    Event myEvent;
    while (mOSWindow->popEvent(&myEvent))
    {
        if (myEvent.Type == Event::EVENT_CLOSED)
        {
            exit(0);
        }
    }
}

void ANGLETestBase::swapBuffers()
{
    if (getGLWindow()->isGLInitialized())
    {
        getGLWindow()->swap();

        if (mEGLWindow)
        {
            EXPECT_EGL_SUCCESS();
        }
    }
}

void ANGLETestBase::setupQuadVertexBuffer(GLfloat positionAttribZ, GLfloat positionAttribXYScale)
{
    if (mQuadVertexBuffer == 0)
    {
        glGenBuffers(1, &mQuadVertexBuffer);
    }

    auto quadVertices = GetQuadVertices();
    for (angle::Vector3 &vertex : quadVertices)
    {
        vertex.x() *= positionAttribXYScale;
        vertex.y() *= positionAttribXYScale;
        vertex.z() = positionAttribZ;
    }

    glBindBuffer(GL_ARRAY_BUFFER, mQuadVertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
}

void ANGLETestBase::setupIndexedQuadVertexBuffer(GLfloat positionAttribZ,
                                                 GLfloat positionAttribXYScale)
{
    if (mQuadVertexBuffer == 0)
    {
        glGenBuffers(1, &mQuadVertexBuffer);
    }

    auto quadVertices = angle::kIndexedQuadVertices;
    for (angle::Vector3 &vertex : quadVertices)
    {
        vertex.x() *= positionAttribXYScale;
        vertex.y() *= positionAttribXYScale;
        vertex.z() = positionAttribZ;
    }

    glBindBuffer(GL_ARRAY_BUFFER, mQuadVertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 4, quadVertices.data(), GL_STATIC_DRAW);
}

void ANGLETestBase::setupIndexedQuadIndexBuffer()
{
    if (mQuadIndexBuffer == 0)
    {
        glGenBuffers(1, &mQuadIndexBuffer);
    }

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mQuadIndexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(angle::kIndexedQuadIndices),
                 angle::kIndexedQuadIndices.data(), GL_STATIC_DRAW);
}

// static
void ANGLETestBase::drawQuad(GLuint program,
                             const std::string &positionAttribName,
                             GLfloat positionAttribZ)
{
    drawQuad(program, positionAttribName, positionAttribZ, 1.0f);
}

// static
void ANGLETestBase::drawQuad(GLuint program,
                             const std::string &positionAttribName,
                             GLfloat positionAttribZ,
                             GLfloat positionAttribXYScale)
{
    drawQuad(program, positionAttribName, positionAttribZ, positionAttribXYScale, false);
}

void ANGLETestBase::drawQuad(GLuint program,
                             const std::string &positionAttribName,
                             GLfloat positionAttribZ,
                             GLfloat positionAttribXYScale,
                             bool useVertexBuffer)
{
    drawQuad(program, positionAttribName, positionAttribZ, positionAttribXYScale, useVertexBuffer,
             false, 0u);
}

void ANGLETestBase::drawQuadInstanced(GLuint program,
                                      const std::string &positionAttribName,
                                      GLfloat positionAttribZ,
                                      GLfloat positionAttribXYScale,
                                      bool useVertexBuffer,
                                      GLuint numInstances)
{
    drawQuad(program, positionAttribName, positionAttribZ, positionAttribXYScale, useVertexBuffer,
             true, numInstances);
}

void ANGLETestBase::drawQuad(GLuint program,
                             const std::string &positionAttribName,
                             GLfloat positionAttribZ,
                             GLfloat positionAttribXYScale,
                             bool useVertexBuffer,
                             bool useInstancedDrawCalls,
                             GLuint numInstances)
{
    GLint previousProgram = 0;
    glGetIntegerv(GL_CURRENT_PROGRAM, &previousProgram);
    if (previousProgram != static_cast<GLint>(program))
    {
        glUseProgram(program);
    }

    GLint previousBuffer = 0;
    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &previousBuffer);

    GLint positionLocation = glGetAttribLocation(program, positionAttribName.c_str());

    std::array<angle::Vector3, 6> quadVertices;

    if (useVertexBuffer)
    {
        setupQuadVertexBuffer(positionAttribZ, positionAttribXYScale);
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
        glBindBuffer(GL_ARRAY_BUFFER, previousBuffer);
    }
    else
    {
        quadVertices = GetQuadVertices();
        for (angle::Vector3 &vertex : quadVertices)
        {
            vertex.x() *= positionAttribXYScale;
            vertex.y() *= positionAttribXYScale;
            vertex.z() = positionAttribZ;
        }

        if (previousBuffer != 0)
        {
            glBindBuffer(GL_ARRAY_BUFFER, 0);
        }
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
        if (previousBuffer != 0)
        {
            glBindBuffer(GL_ARRAY_BUFFER, previousBuffer);
        }
    }
    glEnableVertexAttribArray(positionLocation);

    if (useInstancedDrawCalls)
    {
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, numInstances);
    }
    else
    {
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }

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

    if (previousProgram != static_cast<GLint>(program))
    {
        glUseProgram(previousProgram);
    }
}

void ANGLETestBase::drawIndexedQuad(GLuint program,
                                    const std::string &positionAttribName,
                                    GLfloat positionAttribZ)
{
    drawIndexedQuad(program, positionAttribName, positionAttribZ, 1.0f);
}

void ANGLETestBase::drawIndexedQuad(GLuint program,
                                    const std::string &positionAttribName,
                                    GLfloat positionAttribZ,
                                    GLfloat positionAttribXYScale)
{
    drawIndexedQuad(program, positionAttribName, positionAttribZ, positionAttribXYScale, false);
}

void ANGLETestBase::drawIndexedQuad(GLuint program,
                                    const std::string &positionAttribName,
                                    GLfloat positionAttribZ,
                                    GLfloat positionAttribXYScale,
                                    bool useIndexBuffer)
{
    drawIndexedQuad(program, positionAttribName, positionAttribZ, positionAttribXYScale,
                    useIndexBuffer, false);
}

void ANGLETestBase::drawIndexedQuad(GLuint program,
                                    const std::string &positionAttribName,
                                    GLfloat positionAttribZ,
                                    GLfloat positionAttribXYScale,
                                    bool useIndexBuffer,
                                    bool restrictedRange)
{
    GLint positionLocation = glGetAttribLocation(program, positionAttribName.c_str());

    GLint activeProgram = 0;
    glGetIntegerv(GL_CURRENT_PROGRAM, &activeProgram);
    if (static_cast<GLuint>(activeProgram) != program)
    {
        glUseProgram(program);
    }

    GLuint prevCoordBinding = 0;
    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, reinterpret_cast<GLint *>(&prevCoordBinding));

    setupIndexedQuadVertexBuffer(positionAttribZ, positionAttribXYScale);

    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(positionLocation);
    glBindBuffer(GL_ARRAY_BUFFER, prevCoordBinding);

    GLuint prevIndexBinding = 0;
    const GLvoid *indices;
    if (useIndexBuffer)
    {
        glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING,
                      reinterpret_cast<GLint *>(&prevIndexBinding));

        setupIndexedQuadIndexBuffer();
        indices = 0;
    }
    else
    {
        indices = angle::kIndexedQuadIndices.data();
    }

    if (!restrictedRange)
    {
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
    }
    else
    {
        glDrawRangeElements(GL_TRIANGLES, 0, 3, 6, GL_UNSIGNED_SHORT, indices);
    }

    if (useIndexBuffer)
    {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, prevIndexBinding);
    }

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

    if (static_cast<GLuint>(activeProgram) != program)
    {
        glUseProgram(static_cast<GLuint>(activeProgram));
    }
}

GLuint ANGLETestBase::get2DTexturedQuadProgram()
{
    if (m2DTexturedQuadProgram)
    {
        return m2DTexturedQuadProgram;
    }

    constexpr char kVS[] =
        "attribute vec2 position;\n"
        "varying mediump vec2 texCoord;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = vec4(position, 0, 1);\n"
        "    texCoord = position * 0.5 + vec2(0.5);\n"
        "}\n";

    constexpr char kFS[] =
        "varying mediump vec2 texCoord;\n"
        "uniform sampler2D tex;\n"
        "void main()\n"
        "{\n"
        "    gl_FragColor = texture2D(tex, texCoord);\n"
        "}\n";

    m2DTexturedQuadProgram = CompileProgram(kVS, kFS);
    return m2DTexturedQuadProgram;
}

GLuint ANGLETestBase::get3DTexturedQuadProgram()
{
    if (m3DTexturedQuadProgram)
    {
        return m3DTexturedQuadProgram;
    }

    constexpr char kVS[] = R"(#version 300 es
in vec2 position;
out vec2 texCoord;
void main()
{
    gl_Position = vec4(position, 0, 1);
    texCoord = position * 0.5 + vec2(0.5);
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;

in vec2 texCoord;
out vec4 my_FragColor;

uniform highp sampler3D tex;
uniform float u_layer;

void main()
{
    my_FragColor = texture(tex, vec3(texCoord, u_layer));
})";

    m3DTexturedQuadProgram = CompileProgram(kVS, kFS);
    return m3DTexturedQuadProgram;
}

void ANGLETestBase::draw2DTexturedQuad(GLfloat positionAttribZ,
                                       GLfloat positionAttribXYScale,
                                       bool useVertexBuffer)
{
    ASSERT_NE(0u, get2DTexturedQuadProgram());
    drawQuad(get2DTexturedQuadProgram(), "position", positionAttribZ, positionAttribXYScale,
             useVertexBuffer);
}

void ANGLETestBase::draw3DTexturedQuad(GLfloat positionAttribZ,
                                       GLfloat positionAttribXYScale,
                                       bool useVertexBuffer,
                                       float layer)
{
    GLuint program = get3DTexturedQuadProgram();
    ASSERT_NE(0u, program);
    GLint activeProgram = 0;
    glGetIntegerv(GL_CURRENT_PROGRAM, &activeProgram);
    if (static_cast<GLuint>(activeProgram) != program)
    {
        glUseProgram(program);
    }
    glUniform1f(glGetUniformLocation(program, "u_layer"), layer);

    drawQuad(program, "position", positionAttribZ, positionAttribXYScale, useVertexBuffer);

    if (static_cast<GLuint>(activeProgram) != program)
    {
        glUseProgram(static_cast<GLuint>(activeProgram));
    }
}

void ANGLETestBase::checkD3D11SDKLayersMessages()
{
#if defined(ANGLE_PLATFORM_WINDOWS)
    // On Windows D3D11, check ID3D11InfoQueue to see if any D3D11 SDK Layers messages
    // were outputted by the test. We enable the Debug layers in Release tests as well.
    if (mIgnoreD3D11SDKLayersWarnings ||
        mEGLWindow->getPlatform().renderer != EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE ||
        mEGLWindow->getDisplay() == EGL_NO_DISPLAY)
    {
        return;
    }

    const char *extensionString =
        static_cast<const char *>(eglQueryString(mEGLWindow->getDisplay(), EGL_EXTENSIONS));
    if (!extensionString)
    {
        std::cout << "Error getting extension string from EGL Window." << std::endl;
        return;
    }

    if (!strstr(extensionString, "EGL_EXT_device_query"))
    {
        return;
    }

    EGLAttrib device      = 0;
    EGLAttrib angleDevice = 0;

    PFNEGLQUERYDISPLAYATTRIBEXTPROC queryDisplayAttribEXT;
    PFNEGLQUERYDEVICEATTRIBEXTPROC queryDeviceAttribEXT;

    queryDisplayAttribEXT = reinterpret_cast<PFNEGLQUERYDISPLAYATTRIBEXTPROC>(
        eglGetProcAddress("eglQueryDisplayAttribEXT"));
    queryDeviceAttribEXT = reinterpret_cast<PFNEGLQUERYDEVICEATTRIBEXTPROC>(
        eglGetProcAddress("eglQueryDeviceAttribEXT"));
    ASSERT_NE(nullptr, queryDisplayAttribEXT);
    ASSERT_NE(nullptr, queryDeviceAttribEXT);

    ASSERT_EGL_TRUE(queryDisplayAttribEXT(mEGLWindow->getDisplay(), EGL_DEVICE_EXT, &angleDevice));
    ASSERT_EGL_TRUE(queryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(angleDevice),
                                         EGL_D3D11_DEVICE_ANGLE, &device));
    ID3D11Device *d3d11Device = reinterpret_cast<ID3D11Device *>(device);

    ID3D11InfoQueue *infoQueue = nullptr;
    HRESULT hr =
        d3d11Device->QueryInterface(__uuidof(infoQueue), reinterpret_cast<void **>(&infoQueue));
    if (SUCCEEDED(hr))
    {
        UINT64 numStoredD3DDebugMessages =
            infoQueue->GetNumStoredMessagesAllowedByRetrievalFilter();

        if (numStoredD3DDebugMessages > 0)
        {
            for (UINT64 i = 0; i < numStoredD3DDebugMessages; i++)
            {
                SIZE_T messageLength = 0;
                hr                   = infoQueue->GetMessage(i, nullptr, &messageLength);

                if (SUCCEEDED(hr))
                {
                    D3D11_MESSAGE *pMessage =
                        reinterpret_cast<D3D11_MESSAGE *>(malloc(messageLength));
                    infoQueue->GetMessage(i, pMessage, &messageLength);

                    std::cout << "Message " << i << ":"
                              << " " << pMessage->pDescription << "\n";
                    free(pMessage);
                }
            }

            FAIL() << numStoredD3DDebugMessages
                   << " D3D11 SDK Layers message(s) detected! Test Failed.\n";
        }
    }

    SafeRelease(infoQueue);
#endif  // defined(ANGLE_PLATFORM_WINDOWS)
}

bool ANGLETestBase::hasNvidiaGPU()
{
    for (const angle::GPUDeviceInfo &gpu : ANGLETestEnvironment::GetSystemInfo()->gpus)
    {
        if (angle::IsNvidia(gpu.vendorId))
        {
            return true;
        }
    }
    return false;
}

bool ANGLETestBase::extensionEnabled(const std::string &extName)
{
    return CheckExtensionExists(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)),
                                extName);
}

bool ANGLETestBase::extensionRequestable(const std::string &extName)
{
    return CheckExtensionExists(
        reinterpret_cast<const char *>(glGetString(GL_REQUESTABLE_EXTENSIONS_ANGLE)), extName);
}

bool ANGLETestBase::ensureExtensionEnabled(const std::string &extName)
{
    if (extensionEnabled("GL_ANGLE_request_extension") && extensionRequestable(extName))
    {
        glRequestExtensionANGLE(extName.c_str());
    }

    return extensionEnabled(extName);
}

bool ANGLETestBase::eglDisplayExtensionEnabled(EGLDisplay display, const std::string &extName)
{
    return CheckExtensionExists(eglQueryString(display, EGL_EXTENSIONS), extName);
}

bool ANGLETestBase::eglClientExtensionEnabled(const std::string &extName)
{
    return EGLWindow::ClientExtensionEnabled(extName);
}

bool ANGLETestBase::eglDeviceExtensionEnabled(EGLDeviceEXT device, const std::string &extName)
{
    PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT =
        reinterpret_cast<PFNEGLQUERYDEVICESTRINGEXTPROC>(
            eglGetProcAddress("eglQueryDeviceStringEXT"));
    return CheckExtensionExists(eglQueryDeviceStringEXT(device, EGL_EXTENSIONS), extName);
}

void ANGLETestBase::setWindowWidth(int width)
{
    mWidth = width;
}

void ANGLETestBase::setWindowHeight(int height)
{
    mHeight = height;
}

GLWindowBase *ANGLETestBase::getGLWindow() const
{
    return mWGLWindow ? reinterpret_cast<GLWindowBase *>(mWGLWindow) : mEGLWindow;
}

void ANGLETestBase::setConfigRedBits(int bits)
{
    getGLWindow()->setConfigRedBits(bits);
}

void ANGLETestBase::setConfigGreenBits(int bits)
{
    getGLWindow()->setConfigGreenBits(bits);
}

void ANGLETestBase::setConfigBlueBits(int bits)
{
    getGLWindow()->setConfigBlueBits(bits);
}

void ANGLETestBase::setConfigAlphaBits(int bits)
{
    getGLWindow()->setConfigAlphaBits(bits);
}

void ANGLETestBase::setConfigDepthBits(int bits)
{
    getGLWindow()->setConfigDepthBits(bits);
}

void ANGLETestBase::setConfigStencilBits(int bits)
{
    getGLWindow()->setConfigStencilBits(bits);
}

void ANGLETestBase::setConfigComponentType(EGLenum componentType)
{
    mEGLWindow->setConfigComponentType(componentType);
}

void ANGLETestBase::setMultisampleEnabled(bool enabled)
{
    mEGLWindow->setMultisample(enabled);
}

void ANGLETestBase::setSamples(EGLint samples)
{
    mEGLWindow->setSamples(samples);
}

void ANGLETestBase::setDebugEnabled(bool enabled)
{
    mEGLWindow->setDebugEnabled(enabled);
}

void ANGLETestBase::setNoErrorEnabled(bool enabled)
{
    mEGLWindow->setNoErrorEnabled(enabled);
}

void ANGLETestBase::setWebGLCompatibilityEnabled(bool webglCompatibility)
{
    mEGLWindow->setWebGLCompatibilityEnabled(webglCompatibility);
}

void ANGLETestBase::setExtensionsEnabled(bool extensionsEnabled)
{
    mEGLWindow->setExtensionsEnabled(extensionsEnabled);
}

void ANGLETestBase::setRobustAccess(bool enabled)
{
    mEGLWindow->setRobustAccess(enabled);
}

void ANGLETestBase::setBindGeneratesResource(bool bindGeneratesResource)
{
    mEGLWindow->setBindGeneratesResource(bindGeneratesResource);
}

void ANGLETestBase::setDebugLayersEnabled(bool enabled)
{
    mEGLWindow->setDebugLayersEnabled(enabled);
}

void ANGLETestBase::setClientArraysEnabled(bool enabled)
{
    mEGLWindow->setClientArraysEnabled(enabled);
}

void ANGLETestBase::setRobustResourceInit(bool enabled)
{
    mEGLWindow->setRobustResourceInit(enabled);
}

void ANGLETestBase::setContextProgramCacheEnabled(bool enabled)
{
    mEGLWindow->setContextProgramCacheEnabled(enabled);
}

void ANGLETestBase::setContextVirtualization(bool enabled)
{
    mEGLWindow->setContextVirtualization(enabled);
}

void ANGLETestBase::setDeferContextInit(bool enabled)
{
    mDeferContextInit = enabled;
}

int ANGLETestBase::getClientMajorVersion() const
{
    return getGLWindow()->getClientMajorVersion();
}

int ANGLETestBase::getClientMinorVersion() const
{
    return getGLWindow()->getClientMinorVersion();
}

EGLWindow *ANGLETestBase::getEGLWindow() const
{
    return mEGLWindow;
}

int ANGLETestBase::getWindowWidth() const
{
    return mWidth;
}

int ANGLETestBase::getWindowHeight() const
{
    return mHeight;
}

bool ANGLETestBase::isMultisampleEnabled() const
{
    return mEGLWindow->isMultisample();
}

// static
bool ANGLETestBase::InitTestWindow()
{
    mOSWindow = OSWindow::New();
    if (!mOSWindow->initialize("ANGLE_TEST", 128, 128))
    {
        return false;
    }

    mOSWindow->setVisible(true);

    return true;
}

// static
bool ANGLETestBase::DestroyTestWindow()
{
    if (mOSWindow)
    {
        mOSWindow->destroy();
        OSWindow::Delete(&mOSWindow);
    }

    return true;
}

void ANGLETestBase::SetWindowVisible(bool isVisible)
{
    mOSWindow->setVisible(isVisible);
}

ANGLETest::ANGLETest() : ANGLETestBase(GetParam()) {}

void ANGLETest::SetUp()
{
    ANGLETestBase::ANGLETestSetUp();
}

void ANGLETest::TearDown()
{
    ANGLETestBase::ANGLETestTearDown();
}

bool IsIntel()
{
    std::string rendererString(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
    return (rendererString.find("Intel") != std::string::npos);
}

bool IsAdreno()
{
    std::string rendererString(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
    return (rendererString.find("Adreno") != std::string::npos);
}

bool IsAMD()
{
    std::string rendererString(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
    return (rendererString.find("AMD") != std::string::npos) ||
           (rendererString.find("ATI") != std::string::npos) ||
           (rendererString.find("Radeon") != std::string::npos);
}

bool IsNVIDIA()
{
    std::string rendererString(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
    return (rendererString.find("NVIDIA") != std::string::npos);
}

bool IsD3D11()
{
    std::string rendererString(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
    return (rendererString.find("Direct3D11 vs_5_0") != std::string::npos);
}

bool IsD3D11_FL93()
{
    std::string rendererString(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
    return (rendererString.find("Direct3D11 vs_4_0_") != std::string::npos);
}

bool IsD3D9()
{
    std::string rendererString(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
    return (rendererString.find("Direct3D9") != std::string::npos);
}

bool IsD3DSM3()
{
    return IsD3D9() || IsD3D11_FL93();
}

bool IsDesktopOpenGL()
{
    return IsOpenGL() && !IsOpenGLES();
}

bool IsOpenGLES()
{
    std::string rendererString(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
    return (rendererString.find("OpenGL ES") != std::string::npos);
}

bool IsOpenGL()
{
    std::string rendererString(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
    return (rendererString.find("OpenGL") != std::string::npos);
}

bool IsNULL()
{
    std::string rendererString(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
    return (rendererString.find("NULL") != std::string::npos);
}

bool IsVulkan()
{
    const char *renderer = reinterpret_cast<const char *>(glGetString(GL_RENDERER));
    std::string rendererString(renderer);
    return (rendererString.find("Vulkan") != std::string::npos);
}

bool IsDebug()
{
#if !defined(NDEBUG)
    return true;
#else
    return false;
#endif
}

bool IsRelease()
{
    return !IsDebug();
}

EGLint ANGLETestBase::getPlatformRenderer() const
{
    assert(mEGLWindow);
    return mEGLWindow->getPlatform().renderer;
}

void ANGLETestBase::ignoreD3D11SDKLayersWarnings()
{
    // Some tests may need to disable the D3D11 SDK Layers Warnings checks
    mIgnoreD3D11SDKLayersWarnings = true;
}

void ANGLETestBase::treatPlatformWarningsAsErrors()
{
#if defined(ANGLE_PLATFORM_WINDOWS)
    // Only do warnings-as-errors on 8 and above. We may fall back to the old
    // compiler DLL on Windows 7.
    mPlatformContext.warningsAsErrors = IsWindows8OrGreater();
#endif  // defined(ANGLE_PLATFORM_WINDOWS)
}

ANGLETestBase::ScopedIgnorePlatformMessages::ScopedIgnorePlatformMessages(ANGLETestBase *test)
    : mTest(test)
{
    mTest->mPlatformContext.ignoreMessages = true;
}

ANGLETestBase::ScopedIgnorePlatformMessages::~ScopedIgnorePlatformMessages()
{
    mTest->mPlatformContext.ignoreMessages = false;
}

OSWindow *ANGLETestBase::mOSWindow = nullptr;
Optional<EGLint> ANGLETestBase::mLastRendererType;

std::unique_ptr<angle::Library> ANGLETestEnvironment::gEGLLibrary;
std::unique_ptr<angle::Library> ANGLETestEnvironment::gWGLLibrary;
std::unique_ptr<angle::SystemInfo> ANGLETestEnvironment::gSystemInfo;

void ANGLETestEnvironment::SetUp()
{
    if (!ANGLETestBase::InitTestWindow())
    {
        FAIL() << "Failed to create ANGLE test window.";
    }
}

void ANGLETestEnvironment::TearDown()
{
    ANGLETestBase::DestroyTestWindow();
}

angle::Library *ANGLETestEnvironment::GetEGLLibrary()
{
#if defined(ANGLE_USE_UTIL_LOADER)
    if (!gEGLLibrary)
    {
        gEGLLibrary.reset(angle::OpenSharedLibrary(ANGLE_EGL_LIBRARY_NAME));
    }
#endif  // defined(ANGLE_USE_UTIL_LOADER)
    return gEGLLibrary.get();
}

angle::Library *ANGLETestEnvironment::GetWGLLibrary()
{
#if defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
    if (!gWGLLibrary)
    {
        gWGLLibrary.reset(angle::OpenSharedLibrary("opengl32"));
    }
#endif  // defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
    return gWGLLibrary.get();
}

angle::SystemInfo *ANGLETestEnvironment::GetSystemInfo()
{
    if (!gSystemInfo)
    {
        gSystemInfo = std::make_unique<angle::SystemInfo>();
        if (!angle::GetSystemInfo(gSystemInfo.get()))
        {
            std::cerr << "Failed to get system info." << std::endl;
        }
    }
    return gSystemInfo.get();
}

void ANGLEProcessTestArgs(int *argc, char *argv[])
{
    testing::AddGlobalTestEnvironment(new ANGLETestEnvironment());
}

EGLTest::EGLTest() = default;

EGLTest::~EGLTest() = default;

void EGLTest::SetUp()
{
#if defined(ANGLE_USE_UTIL_LOADER)
    PFNEGLGETPROCADDRESSPROC getProcAddress;
    ANGLETestEnvironment::GetEGLLibrary()->getAs("eglGetProcAddress", &getProcAddress);
    ASSERT_NE(nullptr, getProcAddress);

    angle::LoadEGL(getProcAddress);
    angle::LoadGLES(getProcAddress);
#endif  // defined(ANGLE_USE_UTIL_LOADER)
}

bool IsDisplayExtensionEnabled(EGLDisplay display, const std::string &extName)
{
    return CheckExtensionExists(eglQueryString(display, EGL_EXTENSIONS), extName);
}
