Edit

kc3-lang/angle/src/tests/test_utils/ANGLETest.cpp

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2019-10-20 21:44:36
    Hash : f939cb63
    Message : Revert "Reland "Add more test_utils functions."" This reverts commit 7923e234bc458eda5936b8cb4f09fa19e28c0452. Reason for revert: Fails compilation on rollers: [2097/6048] SOLINK ./lib_angle_unittests__library.cr.so FAILED: lib_angle_unittests__library.cr.so lib_angle_unittests__library.cr.so.TOC lib.unstripped/lib_angle_unittests__library.cr.so python "../../build/toolchain/gcc_solink_wrapper.py" --readelf="../../third_party/android_ndk/toolch...(too long) ld.lld: error: undefined symbol: glCreateShader >>> referenced by shader_utils.cpp >>> angle_util_static/shader_utils.o:(CompileShader(unsigned int, char const*)) in archive obj/third_party/angle/libangle_util_static.a ld.lld: error: undefined symbol: glShaderSource Original change's description: > Reland "Add more test_utils functions." > > This is a reland of 5fcfcea4a9379633a83a67fc1d94938cb31f2a9c > > Re-land uses static linking with angle_util. The root cause > of the CFI error wasn't solved. Static linking works around > the problem by not using any export rules. > > Original change's description: > > Add more test_utils functions. > > > > Includes methods for creating temporary files, deleting files, and > > reading files into a string. Also renames GetPathSeparator to mention > > it's only used for environment variables. Includes a new virtual type > > angle::Process that will be used to implement cross-platform async > > Process launching for tests. Also includes a way to specify a custom > > crash handler callback. > > > > Also adds a few unit tests for the new functionality. They are disabled > > on Android because the functions are not needed by the new test runner. > > > > Bug: angleproject:3162 > > Change-Id: I3e2c2e9837608884c98379fa0f78c9ffbe158d73 > > Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1821940 > > Commit-Queue: Jamie Madill <jmadill@chromium.org> > > Reviewed-by: Jonah Ryan-Davis <jonahr@google.com> > > Bug: chromium:1015810 > Bug: angleproject:3162 > Change-Id: I6a2c1e7b585a13ca846759f32da0777c00d7f7e6 > Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1869541 > Commit-Queue: Jamie Madill <jmadill@chromium.org> > Reviewed-by: Jamie Madill <jmadill@chromium.org> TBR=ynovikov@chromium.org,jonahr@google.com,jmadill@chromium.org Change-Id: I975b2214411906cb981bffa04fa50e0a65ff8b4e No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: chromium:1015810, angleproject:3162 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1870811 Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Jamie Madill <jmadill@chromium.org>

  • src/tests/test_utils/ANGLETest.cpp
  • //
    // Copyright 2015 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    // 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"
    #include "util/test_utils.h"
    
    #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;
    
        GTEST_NONFATAL_FAILURE_(errorMessage);
    
        // Print the stack and stop any crash handling to prevent duplicate reports.
        PrintStackBacktrace();
        TerminateCrashHandler();
    }
    
    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) {}
    
    void TestPlatform_overrideWorkaroundsD3D(PlatformMethods *platform, FeaturesD3D *featuresD3D)
    {
        auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
        if (testPlatformContext->currentTest)
        {
            testPlatformContext->currentTest->overrideWorkaroundsD3D(featuresD3D);
        }
    }
    
    void TestPlatform_overrideFeaturesVk(PlatformMethods *platform, FeaturesVk *featuresVulkan)
    {
        auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
        if (testPlatformContext->currentTest)
        {
            testPlatformContext->currentTest->overrideFeaturesVk(featuresVulkan);
        }
    }
    
    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;
    }
    
    bool ShouldAlwaysForceNewDisplay()
    {
        // We prefer to reuse config displays. This is faster and solves a driver issue where creating
        // many displays causes crashes. However this exposes other driver bugs on many other platforms.
        // Conservatively enable the feature only on Windows Intel and NVIDIA for now.
        SystemInfo *systemInfo = GetTestSystemInfo();
        return (!systemInfo || !IsWindows() || systemInfo->hasAMDGPU());
    }
    }  // anonymous namespace
    
    GLColorRGB::GLColorRGB(const Vector3 &floatColor)
        : R(ColorDenorm(floatColor.x())), G(ColorDenorm(floatColor.y())), B(ColorDenorm(floatColor.z()))
    {}
    
    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
    
    using namespace angle;
    
    PlatformMethods gDefaultPlatformMethods;
    
    namespace
    {
    TestPlatformContext gPlatformContext;
    
    // After a fixed number of iterations we reset the test window. This works around some driver bugs.
    constexpr uint32_t kWindowReuseLimit = 50;
    
    constexpr char kUseConfig[]                = "--use-config=";
    constexpr char kSeparateProcessPerConfig[] = "--separate-process-per-config";
    
    bool RunSeparateProcessesForEachConfig(int *argc, char *argv[])
    {
        std::vector<const char *> commonArgs;
        for (int argIndex = 0; argIndex < *argc; ++argIndex)
        {
            if (strncmp(argv[argIndex], kSeparateProcessPerConfig, strlen(kSeparateProcessPerConfig)) !=
                0)
            {
                commonArgs.push_back(argv[argIndex]);
            }
        }
    
        // Force GoogleTest init now so that we hit the test config init in angle_test_instantiate.cpp.
        // After instantiation is finished we can gather a full list of enabled configs. Then we can
        // iterate the list of configs to spawn a child process for each enabled config.
        testing::InitGoogleTest(argc, argv);
    
        std::vector<std::string> configNames = GetAvailableTestPlatformNames();
    
        bool success = true;
    
        for (const std::string &config : configNames)
        {
            std::stringstream strstr;
            strstr << kUseConfig << config;
    
            std::string configStr = strstr.str();
    
            std::vector<const char *> childArgs = commonArgs;
            childArgs.push_back(configStr.c_str());
    
            int exitCode = 0;
            if (!RunApp(childArgs, nullptr, nullptr, &exitCode))
            {
                std::cerr << "Launching child config " << config << " failed.\n";
            }
            else if (exitCode != 0)
            {
                std::cerr << "Child config " << config << " failed with exit code " << exitCode
                          << ".\n";
                success = false;
            }
        }
        return success;
    }
    }  // anonymous namespace
    
    // static
    std::array<Vector3, 6> ANGLETestBase::GetQuadVertices()
    {
        return kQuadVertices;
    }
    
    // static
    std::array<GLushort, 6> ANGLETestBase::GetQuadIndices()
    {
        return kIndexedQuadIndices;
    }
    
    // static
    std::array<Vector3, 4> ANGLETestBase::GetIndexedQuadVertices()
    {
        return kIndexedQuadVertices;
    }
    
    ANGLETestBase::ANGLETestBase(const PlatformParameters &params)
        : mWidth(16),
          mHeight(16),
          mIgnoreD3D11SDKLayersWarnings(false),
          mQuadVertexBuffer(0),
          mQuadIndexBuffer(0),
          m2DTexturedQuadProgram(0),
          m3DTexturedQuadProgram(0),
          mDeferContextInit(false),
          mAlwaysForceNewDisplay(ShouldAlwaysForceNewDisplay()),
          mForceNewDisplay(mAlwaysForceNewDisplay),
          mSetUpCalled(false),
          mTearDownCalled(false),
          mCurrentParams(nullptr),
          mFixture(nullptr)
    {
        // Override the default platform methods with the ANGLE test methods pointer.
        PlatformParameters withMethods            = params;
        withMethods.eglParameters.platformMethods = &gDefaultPlatformMethods;
    
        auto iter = gFixtures.find(withMethods);
        if (iter != gFixtures.end())
        {
            mCurrentParams = &iter->first;
    
            if (!params.noFixture)
            {
                mFixture = &iter->second;
                mFixture->configParams.reset();
            }
            return;
        }
    
        TestFixture platform;
        auto insertIter = gFixtures.emplace(withMethods, platform);
        mCurrentParams  = &insertIter.first->first;
    
        if (!params.noFixture)
        {
            mFixture = &insertIter.first->second;
            initOSWindow();
        }
    }
    
    void ANGLETestBase::initOSWindow()
    {
        std::stringstream windowNameStream;
        windowNameStream << "ANGLE Tests - " << *mCurrentParams;
        std::string windowName = windowNameStream.str();
    
        if (mAlwaysForceNewDisplay)
        {
            mFixture->osWindow = mOSWindowSingleton;
        }
    
        if (!mFixture->osWindow)
        {
            mFixture->osWindow = OSWindow::New();
            if (!mFixture->osWindow->initialize(windowName.c_str(), 128, 128))
            {
                std::cerr << "Failed to initialize OS Window.";
            }
    
            mOSWindowSingleton = mFixture->osWindow;
        }
    
        // On Linux we must keep the test windows visible. On Windows it doesn't seem to need it.
        mFixture->osWindow->setVisible(!IsWindows());
    
        switch (mCurrentParams->driver)
        {
            case GLESDriverType::AngleEGL:
            {
                mFixture->eglWindow =
                    EGLWindow::New(mCurrentParams->majorVersion, mCurrentParams->minorVersion);
                break;
            }
    
            case GLESDriverType::SystemEGL:
            {
                std::cerr << "Unsupported driver." << std::endl;
                break;
            }
    
            case GLESDriverType::SystemWGL:
            {
                // WGL tests are currently disabled.
                std::cerr << "Unsupported driver." << std::endl;
                break;
            }
        }
    }
    
    ANGLETestBase::~ANGLETestBase()
    {
        if (mQuadVertexBuffer)
        {
            glDeleteBuffers(1, &mQuadVertexBuffer);
        }
        if (mQuadIndexBuffer)
        {
            glDeleteBuffers(1, &mQuadIndexBuffer);
        }
        if (m2DTexturedQuadProgram)
        {
            glDeleteProgram(m2DTexturedQuadProgram);
        }
        if (m3DTexturedQuadProgram)
        {
            glDeleteProgram(m3DTexturedQuadProgram);
        }
    
        if (!mSetUpCalled)
        {
            GTEST_NONFATAL_FAILURE_("SetUp not called.");
        }
    
        if (!mTearDownCalled)
        {
            GTEST_NONFATAL_FAILURE_("TearDown not called.");
        }
    }
    
    void ANGLETestBase::ANGLETestSetUp()
    {
        mSetUpCalled = true;
    
        InitCrashHandler();
    
        gDefaultPlatformMethods.overrideWorkaroundsD3D = TestPlatform_overrideWorkaroundsD3D;
        gDefaultPlatformMethods.overrideFeaturesVk     = TestPlatform_overrideFeaturesVk;
        gDefaultPlatformMethods.logError               = TestPlatform_logError;
        gDefaultPlatformMethods.logWarning             = TestPlatform_logWarning;
        gDefaultPlatformMethods.logInfo                = TestPlatform_logInfo;
        gDefaultPlatformMethods.context                = &gPlatformContext;
    
        gPlatformContext.ignoreMessages   = false;
        gPlatformContext.warningsAsErrors = false;
        gPlatformContext.currentTest      = this;
    
        // TODO(geofflang): Nexus6P generates GL errors during initialization. Suppress error messages
        // temporarily until enough logging is in place to figure out exactly which calls generate
        // errors.  http://crbug.com/998503
        if (IsNexus6P())
        {
            gPlatformContext.ignoreMessages = true;
        }
    
        if (IsWindows())
        {
            const auto &info = testing::UnitTest::GetInstance()->current_test_info();
            WriteDebugMessage("Entering %s.%s\n", info->test_case_name(), info->name());
        }
    
        if (mCurrentParams->noFixture)
        {
    #if defined(ANGLE_USE_UTIL_LOADER)
            PFNEGLGETPROCADDRESSPROC getProcAddress;
            ANGLETestEnvironment::GetEGLLibrary()->getAs("eglGetProcAddress", &getProcAddress);
            ASSERT_NE(nullptr, getProcAddress);
    
            LoadEGL(getProcAddress);
            LoadGLES(getProcAddress);
    #endif  // defined(ANGLE_USE_UTIL_LOADER)
            return;
        }
    
        // 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 (mFixture->osWindow->getWidth() != mWidth || mFixture->osWindow->getHeight() != mHeight)
        {
            if (!mFixture->osWindow->resize(mWidth, mHeight))
            {
                FAIL() << "Failed to resize ANGLE test window.";
            }
            needSwap = true;
        }
    
        // WGL tests are currently disabled.
        if (mFixture->wglWindow)
        {
            FAIL() << "Unsupported driver.";
        }
        else
        {
            if (mForceNewDisplay || !mFixture->eglWindow->isDisplayInitialized())
            {
                mFixture->eglWindow->destroyGL();
                if (!mFixture->eglWindow->initializeDisplay(mFixture->osWindow,
                                                            ANGLETestEnvironment::GetEGLLibrary(),
                                                            mCurrentParams->eglParameters))
                {
                    FAIL() << "EGL Display init failed.";
                }
            }
            else if (mCurrentParams->eglParameters != mFixture->eglWindow->getPlatform())
            {
                FAIL() << "Internal parameter conflict error.";
            }
    
            if (!mFixture->eglWindow->initializeSurface(
                    mFixture->osWindow, ANGLETestEnvironment::GetEGLLibrary(), mFixture->configParams))
            {
                FAIL() << "egl surface init failed.";
            }
    
            if (!mDeferContextInit && !mFixture->eglWindow->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);
    }
    
    void ANGLETestBase::ANGLETestTearDown()
    {
        mTearDownCalled              = true;
        gPlatformContext.currentTest = nullptr;
    
        if (IsWindows())
        {
            const testing::TestInfo *info = testing::UnitTest::GetInstance()->current_test_info();
            WriteDebugMessage("Exiting %s.%s\n", info->test_case_name(), info->name());
        }
    
        if (mCurrentParams->noFixture)
        {
            return;
        }
    
        swapBuffers();
        mFixture->osWindow->messageLoop();
    
        if (mFixture->eglWindow)
        {
            checkD3D11SDKLayersMessages();
        }
    
        if (mFixture->reuseCounter++ >= kWindowReuseLimit || mForceNewDisplay)
        {
            mFixture->reuseCounter = 0;
            getGLWindow()->destroyGL();
        }
        else
        {
            mFixture->eglWindow->destroyContext();
            mFixture->eglWindow->destroySurface();
        }
    
        TerminateCrashHandler();
    
        // Check for quit message
        Event myEvent;
        while (mFixture->osWindow->popEvent(&myEvent))
        {
            if (myEvent.Type == Event::EVENT_CLOSED)
            {
                exit(0);
            }
        }
    }
    
    void ANGLETestBase::ReleaseFixtures()
    {
        for (auto it = gFixtures.begin(); it != gFixtures.end(); it++)
        {
            if (it->second.eglWindow)
            {
                it->second.eglWindow->destroyGL();
            }
        }
    }
    
    void ANGLETestBase::swapBuffers()
    {
        if (getGLWindow()->isGLInitialized())
        {
            getGLWindow()->swap();
    
            if (mFixture->eglWindow)
            {
                EXPECT_EGL_SUCCESS();
            }
        }
    }
    
    void ANGLETestBase::setupQuadVertexBuffer(GLfloat positionAttribZ, GLfloat positionAttribXYScale)
    {
        if (mQuadVertexBuffer == 0)
        {
            glGenBuffers(1, &mQuadVertexBuffer);
        }
    
        auto quadVertices = GetQuadVertices();
        for (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 = kIndexedQuadVertices;
        for (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(kIndexedQuadIndices), 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<Vector3, 6> quadVertices = GetQuadVertices();
    
        if (useVertexBuffer)
        {
            setupQuadVertexBuffer(positionAttribZ, positionAttribXYScale);
            glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
            glBindBuffer(GL_ARRAY_BUFFER, previousBuffer);
        }
        else
        {
            for (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 = 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 ||
            mFixture->eglWindow->getPlatform().renderer != EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE ||
            mFixture->eglWindow->getDisplay() == EGL_NO_DISPLAY)
        {
            return;
        }
    
        const char *extensionString = static_cast<const char *>(
            eglQueryString(mFixture->eglWindow->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;
    
        ASSERT_EGL_TRUE(
            eglQueryDisplayAttribEXT(mFixture->eglWindow->getDisplay(), EGL_DEVICE_EXT, &angleDevice));
        ASSERT_EGL_TRUE(eglQueryDeviceAttribEXT(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);
                    }
                }
                // Clear the queue, so that previous failures are not reported
                // for subsequent, otherwise passing, tests
                infoQueue->ClearStoredMessages();
    
                FAIL() << numStoredD3DDebugMessages
                       << " D3D11 SDK Layers message(s) detected! Test Failed.\n";
            }
        }
    
        SafeRelease(infoQueue);
    #endif  // defined(ANGLE_PLATFORM_WINDOWS)
    }
    
    void ANGLETestBase::setWindowWidth(int width)
    {
        mWidth = width;
    }
    
    void ANGLETestBase::setWindowHeight(int height)
    {
        mHeight = height;
    }
    
    GLWindowBase *ANGLETestBase::getGLWindow() const
    {
        // WGL tests are currently disabled.
        assert(!mFixture->wglWindow);
        return mFixture->eglWindow;
    }
    
    void ANGLETestBase::setConfigRedBits(int bits)
    {
        mFixture->configParams.redBits = bits;
    }
    
    void ANGLETestBase::setConfigGreenBits(int bits)
    {
        mFixture->configParams.greenBits = bits;
    }
    
    void ANGLETestBase::setConfigBlueBits(int bits)
    {
        mFixture->configParams.blueBits = bits;
    }
    
    void ANGLETestBase::setConfigAlphaBits(int bits)
    {
        mFixture->configParams.alphaBits = bits;
    }
    
    void ANGLETestBase::setConfigDepthBits(int bits)
    {
        mFixture->configParams.depthBits = bits;
    }
    
    void ANGLETestBase::setConfigStencilBits(int bits)
    {
        mFixture->configParams.stencilBits = bits;
    }
    
    void ANGLETestBase::setConfigComponentType(EGLenum componentType)
    {
        mFixture->configParams.componentType = componentType;
    }
    
    void ANGLETestBase::setMultisampleEnabled(bool enabled)
    {
        mFixture->configParams.multisample = enabled;
    }
    
    void ANGLETestBase::setSamples(EGLint samples)
    {
        mFixture->configParams.samples = samples;
    }
    
    void ANGLETestBase::setDebugEnabled(bool enabled)
    {
        mFixture->configParams.debug = enabled;
    }
    
    void ANGLETestBase::setNoErrorEnabled(bool enabled)
    {
        mFixture->configParams.noError = enabled;
    }
    
    void ANGLETestBase::setWebGLCompatibilityEnabled(bool webglCompatibility)
    {
        mFixture->configParams.webGLCompatibility = webglCompatibility;
    }
    
    void ANGLETestBase::setExtensionsEnabled(bool extensionsEnabled)
    {
        mFixture->configParams.extensionsEnabled = extensionsEnabled;
    }
    
    void ANGLETestBase::setRobustAccess(bool enabled)
    {
        mFixture->configParams.robustAccess = enabled;
    }
    
    void ANGLETestBase::setBindGeneratesResource(bool bindGeneratesResource)
    {
        mFixture->configParams.bindGeneratesResource = bindGeneratesResource;
    }
    
    void ANGLETestBase::setClientArraysEnabled(bool enabled)
    {
        mFixture->configParams.clientArraysEnabled = enabled;
    }
    
    void ANGLETestBase::setRobustResourceInit(bool enabled)
    {
        mFixture->configParams.robustResourceInit = enabled;
    }
    
    void ANGLETestBase::setContextProgramCacheEnabled(bool enabled)
    {
        mFixture->configParams.contextProgramCacheEnabled = enabled;
    }
    
    void ANGLETestBase::setContextResetStrategy(EGLenum resetStrategy)
    {
        mFixture->configParams.resetStrategy = resetStrategy;
    }
    
    void ANGLETestBase::forceNewDisplay()
    {
        mForceNewDisplay = true;
    }
    
    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 mFixture->eglWindow;
    }
    
    int ANGLETestBase::getWindowWidth() const
    {
        return mWidth;
    }
    
    int ANGLETestBase::getWindowHeight() const
    {
        return mHeight;
    }
    
    bool ANGLETestBase::isMultisampleEnabled() const
    {
        return mFixture->eglWindow->isMultisample();
    }
    
    void ANGLETestBase::setWindowVisible(bool isVisible)
    {
        mFixture->osWindow->setVisible(isVisible);
    }
    
    bool IsAdreno()
    {
        std::string rendererString(reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
        return (rendererString.find("Adreno") != 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();
    }
    
    ANGLETestBase::TestFixture::TestFixture()  = default;
    ANGLETestBase::TestFixture::~TestFixture() = default;
    
    EGLint ANGLETestBase::getPlatformRenderer() const
    {
        assert(mFixture->eglWindow);
        return mFixture->eglWindow->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.
        gPlatformContext.warningsAsErrors = IsWindows8OrGreater();
    #endif  // defined(ANGLE_PLATFORM_WINDOWS)
    }
    
    ANGLETestBase::ScopedIgnorePlatformMessages::ScopedIgnorePlatformMessages()
    {
        gPlatformContext.ignoreMessages = true;
    }
    
    ANGLETestBase::ScopedIgnorePlatformMessages::~ScopedIgnorePlatformMessages()
    {
        gPlatformContext.ignoreMessages = false;
    }
    
    OSWindow *ANGLETestBase::mOSWindowSingleton = nullptr;
    std::map<angle::PlatformParameters, ANGLETestBase::TestFixture> ANGLETestBase::gFixtures;
    Optional<EGLint> ANGLETestBase::mLastRendererType;
    
    std::unique_ptr<Library> ANGLETestEnvironment::gEGLLibrary;
    std::unique_ptr<Library> ANGLETestEnvironment::gWGLLibrary;
    
    void ANGLETestEnvironment::SetUp() {}
    
    void ANGLETestEnvironment::TearDown()
    {
        ANGLETestBase::ReleaseFixtures();
    }
    
    Library *ANGLETestEnvironment::GetEGLLibrary()
    {
    #if defined(ANGLE_USE_UTIL_LOADER)
        if (!gEGLLibrary)
        {
            gEGLLibrary.reset(OpenSharedLibrary(ANGLE_EGL_LIBRARY_NAME, SearchType::ApplicationDir));
        }
    #endif  // defined(ANGLE_USE_UTIL_LOADER)
        return gEGLLibrary.get();
    }
    
    Library *ANGLETestEnvironment::GetWGLLibrary()
    {
    #if defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
        if (!gWGLLibrary)
        {
            gWGLLibrary.reset(OpenSharedLibrary("opengl32", SearchType::SystemDir));
        }
    #endif  // defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
        return gWGLLibrary.get();
    }
    
    void ANGLEProcessTestArgs(int *argc, char *argv[])
    {
        testing::AddGlobalTestEnvironment(new ANGLETestEnvironment());
    
        for (int argIndex = 1; argIndex < *argc; argIndex++)
        {
            if (strncmp(argv[argIndex], kUseConfig, strlen(kUseConfig)) == 0)
            {
                gSelectedConfig = std::string(argv[argIndex] + strlen(kUseConfig));
            }
            if (strncmp(argv[argIndex], kSeparateProcessPerConfig, strlen(kSeparateProcessPerConfig)) ==
                0)
            {
                gSeparateProcessPerConfig = true;
            }
        }
    
        if (gSeparateProcessPerConfig)
        {
            if (!gSelectedConfig.empty())
            {
                std::cout << "Cannot use both a single test config and separate processes.\n";
                exit(1);
            }
    
            if (RunSeparateProcessesForEachConfig(argc, argv))
            {
                exit(0);
            }
            else
            {
                std::cout << "Some subprocesses failed.\n";
                exit(1);
            }
        }
    }
    
    bool EnsureGLExtensionEnabled(const std::string &extName)
    {
        if (IsGLExtensionEnabled("GL_ANGLE_request_extension") && IsGLExtensionRequestable(extName))
        {
            glRequestExtensionANGLE(extName.c_str());
        }
    
        return IsGLExtensionEnabled(extName);
    }
    
    bool IsEGLClientExtensionEnabled(const std::string &extName)
    {
        return CheckExtensionExists(eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS), extName);
    }
    
    bool IsEGLDeviceExtensionEnabled(EGLDeviceEXT device, const std::string &extName)
    {
        return CheckExtensionExists(eglQueryDeviceStringEXT(device, EGL_EXTENSIONS), extName);
    }
    
    bool IsEGLDisplayExtensionEnabled(EGLDisplay display, const std::string &extName)
    {
        return CheckExtensionExists(eglQueryString(display, EGL_EXTENSIONS), extName);
    }
    
    bool IsGLExtensionEnabled(const std::string &extName)
    {
        return CheckExtensionExists(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)),
                                    extName);
    }
    
    bool IsGLExtensionRequestable(const std::string &extName)
    {
        return CheckExtensionExists(
            reinterpret_cast<const char *>(glGetString(GL_REQUESTABLE_EXTENSIONS_ANGLE)), extName);
    }