// // 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 #include #include "common/platform.h" #include "gpu_info_util/SystemInfo.h" #include "test_utils/runner/TestSuite.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 #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(channelValue) / 255.0f; } GLubyte ColorDenorm(float colorValue) { return static_cast(colorValue * 255.0f); } void TestPlatform_logError(PlatformMethods *platform, const char *errorMessage) { auto *testPlatformContext = static_cast(platform->context); if (testPlatformContext->ignoreMessages) return; GTEST_NONFATAL_FAILURE_(errorMessage); PrintStackBacktrace(); } void TestPlatform_logWarning(PlatformMethods *platform, const char *warningMessage) { auto *testPlatformContext = static_cast(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(platform->context); if (testPlatformContext->currentTest) { testPlatformContext->currentTest->overrideWorkaroundsD3D(featuresD3D); } } void TestPlatform_overrideFeaturesVk(PlatformMethods *platform, FeaturesVk *featuresVulkan) { auto *testPlatformContext = static_cast(platform->context); if (testPlatformContext->currentTest) { testPlatformContext->currentTest->overrideFeaturesVk(featuresVulkan); } } const std::array 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 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 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; } // Always re-use displays when using --bot-mode in the test runner. bool gReuseDisplays = false; bool ShouldAlwaysForceNewDisplay() { if (gReuseDisplays) return false; // 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()); } GPUTestConfig::API GetTestConfigAPIFromRenderer(EGLenum renderer, EGLenum deviceType) { switch (renderer) { case EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE: return GPUTestConfig::kAPID3D11; case EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE: return GPUTestConfig::kAPID3D9; case EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE: return GPUTestConfig::kAPIGLDesktop; case EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE: return GPUTestConfig::kAPIGLES; case EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE: if (deviceType == EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE) { return GPUTestConfig::kAPISwiftShader; } else { return GPUTestConfig::kAPIVulkan; } case EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE: return GPUTestConfig::kAPIMetal; default: std::cerr << "Unknown Renderer enum: 0x%X\n" << renderer; return GPUTestConfig::kAPIUnknown; } } } // 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)); } GLuint GLColor::asUint() const { GLuint uint = 0; memcpy(&uint, &R, sizeof(GLuint)); return uint; } 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 &pixelPoints, int windowWidth, int windowHeight, std::vector *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(color.R) << ", " << static_cast(color.G) << ", " << static_cast(color.B) << ", " << static_cast(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 kReuseDisplays[] = "--reuse-displays"; constexpr char kEnableANGLEPerTestCaptureLabel[] = "--angle-per-test-capture-label"; constexpr char kBatchId[] = "--batch-id="; constexpr char kDelayTestStart[] = "--delay-test-start="; constexpr char kRenderDoc[] = "--renderdoc"; void SetupEnvironmentVarsForCaptureReplay() { const ::testing::TestInfo *const testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); std::string testName = std::string{testInfo->name()}; std::replace(testName.begin(), testName.end(), '/', '_'); SetEnvironmentVar("ANGLE_CAPTURE_LABEL", (std::string{testInfo->test_case_name()} + "_" + testName).c_str()); } } // anonymous namespace int gTestStartDelaySeconds = 0; int GetTestStartDelaySeconds() { return gTestStartDelaySeconds; } void SetTestStartDelay(const char *testStartDelay) { gTestStartDelaySeconds = std::stoi(testStartDelay); } bool gEnableRenderDocCapture = false; // static std::array ANGLETestBase::GetQuadVertices() { return kQuadVertices; } // static std::array ANGLETestBase::GetQuadIndices() { return kIndexedQuadIndices; } // static std::array 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; if (withMethods.getRenderer() == EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE) { #if defined(ANGLE_ENABLE_VULKAN_VALIDATION_LAYERS) withMethods.eglParameters.debugLayersEnabled = true; #else withMethods.eglParameters.debugLayersEnabled = false; #endif } if (gEnableRenderDocCapture) { mRenderDoc.attach(); } 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(); mFixture->osWindow->disableErrorMessageDialog(); if (!mFixture->osWindow->initialize(windowName.c_str(), 128, 128)) { std::cerr << "Failed to initialize OS Window.\n"; } if (IsAndroid()) { // Initialize the single window on Andoird only once mOSWindowSingleton = mFixture->osWindow; } } if (!mFixture->osWindow->valid()) { return; } // 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; // Delay test startup to allow a debugger to attach. if (GetTestStartDelaySeconds()) { angle::Sleep(GetTestStartDelaySeconds() * 1000); } 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; const testing::TestInfo *testInfo = testing::UnitTest::GetInstance()->current_test_info(); // Check the skip list. angle::GPUTestConfig::API api = GetTestConfigAPIFromRenderer(mCurrentParams->getRenderer(), mCurrentParams->getDeviceType()); GPUTestConfig testConfig = GPUTestConfig(api, 0); std::stringstream fullTestNameStr; fullTestNameStr << testInfo->test_case_name() << "." << testInfo->name(); std::string fullTestName = fullTestNameStr.str(); TestSuite *testSuite = TestSuite::GetInstance(); int32_t testExpectation = testSuite->getTestExpectationWithConfig(testConfig, fullTestName); if (testExpectation == GPUTestExpectationsParser::kGpuTestSkip) { GTEST_SKIP() << "Test skipped on this config"; } if (IsWindows()) { WriteDebugMessage("Entering %s\n", fullTestName.c_str()); } if (mCurrentParams->noFixture) { LoadEntryPointsWithUtilLoader(mCurrentParams->driver); mIsSetUp = true; return; } if (mLastLoadedDriver.valid() && mCurrentParams->driver != mLastLoadedDriver.value()) { LoadEntryPointsWithUtilLoader(mCurrentParams->driver); mLastLoadedDriver = mCurrentParams->driver; } if (gEnableANGLEPerTestCaptureLabel) { SetupEnvironmentVarsForCaptureReplay(); } if (!mFixture->osWindow->valid()) { mIsSetUp = true; 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; int osWindowWidth = mFixture->osWindow->getWidth(); int osWindowHeight = mFixture->osWindow->getHeight(); const bool isRotated = mCurrentParams->eglParameters.emulatedPrerotation == 90 || mCurrentParams->eglParameters.emulatedPrerotation == 270; if (isRotated) { std::swap(osWindowWidth, osWindowHeight); } if (osWindowWidth != mWidth || osWindowHeight != mHeight) { int newWindowWidth = mWidth; int newWindowHeight = mHeight; if (isRotated) { std::swap(newWindowWidth, newWindowHeight); } if (!mFixture->osWindow->resize(newWindowWidth, newWindowHeight)) { 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); mIsSetUp = true; mRenderDoc.startFrame(); } void ANGLETestBase::ANGLETestPreTearDown() { // We swap an extra time before we call "tearDown" to capture resources before they're freed. if (gEnableANGLEPerTestCaptureLabel) { swapBuffers(); } } 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 || !mFixture->osWindow->valid()) { mRenderDoc.endFrame(); return; } swapBuffers(); mFixture->osWindow->messageLoop(); mRenderDoc.endFrame(); if (mFixture->eglWindow) { checkD3D11SDKLayersMessages(); } if (mFixture->reuseCounter++ >= kWindowReuseLimit || mForceNewDisplay) { if (!mForceNewDisplay) { printf("Recreating test window because of reuse limit of %d\n", kWindowReuseLimit); } 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, 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, false, numInstances); } void ANGLETestBase::drawPatches(GLuint program, const std::string &positionAttribName, GLfloat positionAttribZ, GLfloat positionAttribXYScale, bool useVertexBuffer) { drawQuad(program, positionAttribName, positionAttribZ, positionAttribXYScale, useVertexBuffer, false, true, 0u); } void ANGLETestBase::drawQuad(GLuint program, const std::string &positionAttribName, GLfloat positionAttribZ, GLfloat positionAttribXYScale, bool useVertexBuffer, bool useInstancedDrawCalls, bool useTessellationPatches, GLuint numInstances) { GLint previousProgram = 0; glGetIntegerv(GL_CURRENT_PROGRAM, &previousProgram); if (previousProgram != static_cast(program)) { glUseProgram(program); } GLint previousBuffer = 0; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &previousBuffer); GLint positionLocation = glGetAttribLocation(program, positionAttribName.c_str()); std::array 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); GLenum drawMode = (useTessellationPatches) ? GL_PATCHES : GL_TRIANGLES; if (useInstancedDrawCalls) { glDrawArraysInstanced(drawMode, 0, 6, numInstances); } else { glDrawArrays(drawMode, 0, 6); } glDisableVertexAttribArray(positionLocation); glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr); if (previousProgram != static_cast(program)) { glUseProgram(previousProgram); } } void ANGLETestBase::drawQuadPPO(GLuint vertProgram, const std::string &positionAttribName, const GLfloat positionAttribZ, const GLfloat positionAttribXYScale) { GLint activeProgram = 0; glGetIntegerv(GL_CURRENT_PROGRAM, &activeProgram); if (activeProgram) { glUseProgram(0); } std::array quadVertices = GetQuadVertices(); for (Vector3 &vertex : quadVertices) { vertex.x() *= positionAttribXYScale; vertex.y() *= positionAttribXYScale; vertex.z() = positionAttribZ; } GLint positionLocation = glGetAttribLocation(vertProgram, positionAttribName.c_str()); glBindBuffer(GL_ARRAY_BUFFER, 0); glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data()); glEnableVertexAttribArray(positionLocation); glDrawArrays(GL_TRIANGLES, 0, 6); glDisableVertexAttribArray(positionLocation); glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr); if (activeProgram) { glUseProgram(static_cast(activeProgram)); } } 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) { ASSERT(!mFixture->configParams.webGLCompatibility.valid() || !mFixture->configParams.webGLCompatibility.value()); 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(activeProgram) != program) { glUseProgram(program); } GLuint prevCoordBinding = 0; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, reinterpret_cast(&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(&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(activeProgram) != program) { glUseProgram(static_cast(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(activeProgram) != program) { glUseProgram(program); } glUniform1f(glGetUniformLocation(program, "u_layer"), layer); drawQuad(program, "position", positionAttribZ, positionAttribXYScale, useVertexBuffer); if (static_cast(activeProgram) != program) { glUseProgram(static_cast(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( 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(angleDevice), EGL_D3D11_DEVICE_ANGLE, &device)); ID3D11Device *d3d11Device = reinterpret_cast(device); ID3D11InfoQueue *infoQueue = nullptr; HRESULT hr = d3d11Device->QueryInterface(__uuidof(infoQueue), reinterpret_cast(&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(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::isEmulatedPrerotation() const { return mCurrentParams->eglParameters.emulatedPrerotation != 0; } 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 ANGLETestBase::gFixtures; Optional ANGLETestBase::mLastRendererType; Optional ANGLETestBase::mLastLoadedDriver; std::unique_ptr ANGLETestEnvironment::gAngleEGLLibrary; std::unique_ptr ANGLETestEnvironment::gSystemEGLLibrary; std::unique_ptr 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::ModuleDir)); } #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(), SearchType::SystemDir)); } #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)); } else if (strncmp(argv[argIndex], kReuseDisplays, strlen(kReuseDisplays)) == 0) { gReuseDisplays = true; } else if (strncmp(argv[argIndex], kBatchId, strlen(kBatchId)) == 0) { // Enable display reuse when running under --bot-mode. gReuseDisplays = true; } else if (strncmp(argv[argIndex], kEnableANGLEPerTestCaptureLabel, strlen(kEnableANGLEPerTestCaptureLabel)) == 0) { gEnableANGLEPerTestCaptureLabel = true; } else if (strncmp(argv[argIndex], kDelayTestStart, strlen(kDelayTestStart)) == 0) { SetTestStartDelay(argv[argIndex] + strlen(kDelayTestStart)); } else if (strncmp(argv[argIndex], kRenderDoc, strlen(kRenderDoc)) == 0) { gEnableRenderDocCapture = true; } } }