| // |
| // 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/random_utils.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); |
| |
| PrintStackBacktrace(); |
| } |
| |
| 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 RandomColor(angle::RNG *rng) |
| { |
| return GLColor(rng->randomIntBetween(0, 255), rng->randomIntBetween(0, 255), |
| rng->randomIntBetween(0, 255), rng->randomIntBetween(0, 255)); |
| } |
| |
| 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; |
| } |
| |
| void LoadEntryPointsWithUtilLoader(angle::GLESDriverType driverType) |
| { |
| #if defined(ANGLE_USE_UTIL_LOADER) |
| PFNEGLGETPROCADDRESSPROC getProcAddress; |
| ANGLETestEnvironment::GetDriverLibrary(driverType)->getAs("eglGetProcAddress", &getProcAddress); |
| ASSERT_NE(nullptr, getProcAddress); |
| |
| LoadEGL(getProcAddress); |
| LoadGLES(getProcAddress); |
| #endif // defined(ANGLE_USE_UTIL_LOADER) |
| } |
| } // 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()); |
| |
| ProcessHandle process(childArgs, false, false); |
| if (!process->started() || !process->finish()) |
| { |
| std::cerr << "Launching child config " << config << " failed.\n"; |
| } |
| else if (process->getExitCode() != 0) |
| { |
| std::cerr << "Child config " << config << " failed with exit code " |
| << process->getExitCode() << ".\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 ¶ms) |
| : 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; |
| |
| // We don't build vulkan debug layers on Mac (http://anglebug.com/4376) |
| if (IsOSX() && withMethods.getRenderer() == EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE) |
| { |
| withMethods.eglParameters.debugLayersEnabled = false; |
| } |
| |
| 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 (IsAndroid()) |
| { |
| // Only one window per test application on Android, shared among all fixtures |
| 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."; |
| } |
| |
| if (IsAndroid()) |
| { |
| // Initialize the single window on Andoird only once |
| mOSWindowSingleton = mFixture->osWindow; |
| } |
| } |
| |
| // On Linux we must keep the test windows visible. On Windows it doesn't seem to need it. |
| setWindowVisible(getOSWindow(), !IsWindows()); |
| |
| switch (mCurrentParams->driver) |
| { |
| case GLESDriverType::AngleEGL: |
| case GLESDriverType::SystemEGL: |
| { |
| mFixture->eglWindow = |
| EGLWindow::New(mCurrentParams->majorVersion, mCurrentParams->minorVersion); |
| 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; |
| |
| 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) |
| { |
| LoadEntryPointsWithUtilLoader(mCurrentParams->driver); |
| return; |
| } |
| |
| if (mLastLoadedDriver.valid() && mCurrentParams->driver != mLastLoadedDriver.value()) |
| { |
| LoadEntryPointsWithUtilLoader(mCurrentParams->driver); |
| mLastLoadedDriver = mCurrentParams->driver; |
| } |
| |
| // 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 |
| { |
| Library *driverLib = ANGLETestEnvironment::GetDriverLibrary(mCurrentParams->driver); |
| |
| if (mForceNewDisplay || !mFixture->eglWindow->isDisplayInitialized()) |
| { |
| mFixture->eglWindow->destroyGL(); |
| if (!mFixture->eglWindow->initializeDisplay(mFixture->osWindow, driverLib, |
| mCurrentParams->driver, |
| 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, driverLib, |
| 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(); |
| } |
| |
| // 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)); |
| } |
| } |
| |
| bool ANGLETestBase::platformSupportsMultithreading() const |
| { |
| return (IsOpenGLES() && IsAndroid()) || IsVulkan(); |
| } |
| |
| 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(OSWindow *osWindow, bool isVisible) |
| { |
| // SwiftShader windows are not required to be visible for test correctness, |
| // moreover, making a SwiftShader window visible flaky hangs on Xvfb, so we keep them hidden. |
| if (isSwiftshader()) |
| { |
| return; |
| } |
| osWindow->setVisible(isVisible); |
| } |
| |
| 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; |
| Optional<angle::GLESDriverType> ANGLETestBase::mLastLoadedDriver; |
| |
| std::unique_ptr<Library> ANGLETestEnvironment::gAngleEGLLibrary; |
| std::unique_ptr<Library> ANGLETestEnvironment::gSystemEGLLibrary; |
| std::unique_ptr<Library> ANGLETestEnvironment::gSystemWGLLibrary; |
| |
| void ANGLETestEnvironment::SetUp() {} |
| |
| void ANGLETestEnvironment::TearDown() |
| { |
| ANGLETestBase::ReleaseFixtures(); |
| } |
| |
| // static |
| Library *ANGLETestEnvironment::GetDriverLibrary(angle::GLESDriverType driver) |
| { |
| switch (driver) |
| { |
| case angle::GLESDriverType::AngleEGL: |
| return GetAngleEGLLibrary(); |
| case angle::GLESDriverType::SystemEGL: |
| return GetSystemEGLLibrary(); |
| case angle::GLESDriverType::SystemWGL: |
| return GetSystemWGLLibrary(); |
| default: |
| return nullptr; |
| } |
| } |
| |
| // static |
| Library *ANGLETestEnvironment::GetAngleEGLLibrary() |
| { |
| #if defined(ANGLE_USE_UTIL_LOADER) |
| if (!gAngleEGLLibrary) |
| { |
| gAngleEGLLibrary.reset( |
| OpenSharedLibrary(ANGLE_EGL_LIBRARY_NAME, SearchType::ApplicationDir)); |
| } |
| #endif // defined(ANGLE_USE_UTIL_LOADER) |
| return gAngleEGLLibrary.get(); |
| } |
| |
| // static |
| Library *ANGLETestEnvironment::GetSystemEGLLibrary() |
| { |
| #if defined(ANGLE_USE_UTIL_LOADER) |
| if (!gSystemEGLLibrary) |
| { |
| gSystemEGLLibrary.reset( |
| OpenSharedLibraryWithExtension(GetNativeEGLLibraryNameWithExtension())); |
| } |
| #endif // defined(ANGLE_USE_UTIL_LOADER) |
| return gSystemEGLLibrary.get(); |
| } |
| |
| // static |
| Library *ANGLETestEnvironment::GetSystemWGLLibrary() |
| { |
| #if defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS) |
| if (!gSystemWGLLibrary) |
| { |
| gSystemWGLLibrary.reset(OpenSharedLibrary("opengl32", SearchType::SystemDir)); |
| } |
| #endif // defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS) |
| return gSystemWGLLibrary.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) |
| { |
| SetSelectedConfig(argv[argIndex] + strlen(kUseConfig)); |
| } |
| if (strncmp(argv[argIndex], kSeparateProcessPerConfig, strlen(kSeparateProcessPerConfig)) == |
| 0) |
| { |
| gSeparateProcessPerConfig = true; |
| } |
| } |
| |
| if (gSeparateProcessPerConfig) |
| { |
| if (IsConfigSelected()) |
| { |
| 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); |
| } |
| } |
| } |