// // Copyright 2014 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. // // ANGLEPerfTests: // Base class for google test performance tests // #include "ANGLEPerfTest.h" #if defined(ANGLE_PLATFORM_ANDROID) # include # include #endif #include "ANGLEPerfTestArgs.h" #include "common/base/anglebase/trace_event/trace_event.h" #include "common/debug.h" #include "common/gl_enum_utils.h" #include "common/mathutil.h" #include "common/platform.h" #include "common/string_utils.h" #include "common/system_utils.h" #include "common/utilities.h" #include "test_utils/runner/TestSuite.h" #include "third_party/perf/perf_test.h" #include "util/shader_utils.h" #include "util/test_utils.h" #include #include #include #include #include #include #include #include #include #include #include #if defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS) # include "util/windows/WGLWindow.h" #endif // defined(ANGLE_USE_UTIL_LOADER) &&defined(ANGLE_PLATFORM_WINDOWS) using namespace angle; namespace js = rapidjson; namespace { constexpr size_t kInitialTraceEventBufferSize = 50000; constexpr double kMilliSecondsPerSecond = 1e3; constexpr double kMicroSecondsPerSecond = 1e6; constexpr double kNanoSecondsPerSecond = 1e9; constexpr size_t kNumberOfStepsPerformedToComputeGPUTime = 16; constexpr char kPeakMemoryMetric[] = ".memory_max"; constexpr char kMedianMemoryMetric[] = ".memory_median"; struct TraceCategory { unsigned char enabled; const char *name; }; constexpr TraceCategory gTraceCategories[2] = { {1, "gpu.angle"}, {1, "gpu.angle.gpu"}, }; void EmptyPlatformMethod(PlatformMethods *, const char *) {} void CustomLogError(PlatformMethods *platform, const char *errorMessage) { auto *angleRenderTest = static_cast(platform->context); angleRenderTest->onErrorMessage(errorMessage); } TraceEventHandle AddPerfTraceEvent(PlatformMethods *platform, char phase, const unsigned char *categoryEnabledFlag, const char *name, unsigned long long id, double timestamp, int numArgs, const char **argNames, const unsigned char *argTypes, const unsigned long long *argValues, unsigned char flags) { if (!gEnableTrace) return 0; // Discover the category name based on categoryEnabledFlag. This flag comes from the first // parameter of TraceCategory, and corresponds to one of the entries in gTraceCategories. static_assert(offsetof(TraceCategory, enabled) == 0, "|enabled| must be the first field of the TraceCategory class."); const TraceCategory *category = reinterpret_cast(categoryEnabledFlag); ANGLERenderTest *renderTest = static_cast(platform->context); std::lock_guard lock(renderTest->getTraceEventMutex()); uint32_t tid = renderTest->getCurrentThreadSerial(); std::vector &buffer = renderTest->getTraceEventBuffer(); buffer.emplace_back(phase, category->name, name, timestamp, tid); return buffer.size(); } const unsigned char *GetPerfTraceCategoryEnabled(PlatformMethods *platform, const char *categoryName) { if (gEnableTrace) { for (const TraceCategory &category : gTraceCategories) { if (strcmp(category.name, categoryName) == 0) { return &category.enabled; } } } constexpr static unsigned char kZero = 0; return &kZero; } void UpdateTraceEventDuration(PlatformMethods *platform, const unsigned char *categoryEnabledFlag, const char *name, TraceEventHandle eventHandle) { // Not implemented. } double MonotonicallyIncreasingTime(PlatformMethods *platform) { return GetHostTimeSeconds(); } bool WriteJsonFile(const std::string &outputFile, js::Document *doc) { FILE *fp = fopen(outputFile.c_str(), "w"); if (!fp) { return false; } constexpr size_t kBufferSize = 0xFFFF; std::vector writeBuffer(kBufferSize); js::FileWriteStream os(fp, writeBuffer.data(), kBufferSize); js::PrettyWriter writer(os); if (!doc->Accept(writer)) { fclose(fp); return false; } fclose(fp); return true; } void DumpTraceEventsToJSONFile(const std::vector &traceEvents, const char *outputFileName) { js::Document doc(js::kObjectType); js::Document::AllocatorType &allocator = doc.GetAllocator(); js::Value events(js::kArrayType); for (const TraceEvent &traceEvent : traceEvents) { js::Value value(js::kObjectType); const uint64_t microseconds = static_cast(traceEvent.timestamp * 1000.0 * 1000.0); js::Document::StringRefType eventName(traceEvent.name); js::Document::StringRefType categoryName(traceEvent.categoryName); js::Document::StringRefType pidName( strcmp(traceEvent.categoryName, "gpu.angle.gpu") == 0 ? "GPU" : "ANGLE"); value.AddMember("name", eventName, allocator); value.AddMember("cat", categoryName, allocator); value.AddMember("ph", std::string(1, traceEvent.phase), allocator); value.AddMember("ts", microseconds, allocator); value.AddMember("pid", pidName, allocator); value.AddMember("tid", traceEvent.tid, allocator); events.PushBack(value, allocator); } doc.AddMember("traceEvents", events, allocator); if (WriteJsonFile(outputFileName, &doc)) { printf("Wrote trace file to %s\n", outputFileName); } else { printf("Error writing trace file to %s\n", outputFileName); } } [[maybe_unused]] void KHRONOS_APIENTRY PerfTestDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) { // Early exit on non-errors. if (type != GL_DEBUG_TYPE_ERROR || !userParam) { return; } ANGLERenderTest *renderTest = const_cast(reinterpret_cast(userParam)); renderTest->onErrorMessage(message); } double ComputeMean(const std::vector &values) { double sum = std::accumulate(values.begin(), values.end(), 0.0); double mean = sum / static_cast(values.size()); return mean; } void FinishAndCheckForContextLoss() { glFinish(); if (glGetError() == GL_CONTEXT_LOST) { FAIL() << "Context lost"; } } #if defined(ANGLE_PLATFORM_ANDROID) constexpr bool kHasATrace = true; void *gLibAndroid = nullptr; bool (*gATraceIsEnabled)(void); bool (*gATraceSetCounter)(const char *counterName, int64_t counterValue); void SetupATrace() { if (gLibAndroid == nullptr) { gLibAndroid = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL); gATraceIsEnabled = (decltype(gATraceIsEnabled))dlsym(gLibAndroid, "ATrace_isEnabled"); gATraceSetCounter = (decltype(gATraceSetCounter))dlsym(gLibAndroid, "ATrace_setCounter"); } } bool ATraceEnabled() { return gATraceIsEnabled(); } #else constexpr bool kHasATrace = false; void SetupATrace() {} bool ATraceEnabled() { return false; } #endif } // anonymous namespace TraceEvent::TraceEvent(char phaseIn, const char *categoryNameIn, const char *nameIn, double timestampIn, uint32_t tidIn) : phase(phaseIn), categoryName(categoryNameIn), name{}, timestamp(timestampIn), tid(tidIn) { ASSERT(strlen(nameIn) < kMaxNameLen); strcpy(name, nameIn); } ANGLEPerfTest::ANGLEPerfTest(const std::string &name, const std::string &backend, const std::string &story, unsigned int iterationsPerStep, const char *units) : mName(name), mBackend(backend), mStory(story), mGPUTimeNs(0), mSkipTest(false), mStepsToRun(std::max(gStepsPerTrial, gMaxStepsPerformed)), mTrialNumStepsPerformed(0), mTotalNumStepsPerformed(0), mIterationsPerStep(iterationsPerStep), mRunning(true), mPerfMonitor(0) { if (mStory == "") { mStory = "baseline_story"; } if (mStory[0] == '_') { mStory = mStory.substr(1); } mReporter = std::make_unique(mName + mBackend, mStory); mReporter->RegisterImportantMetric(".wall_time", units); mReporter->RegisterImportantMetric(".cpu_time", units); mReporter->RegisterImportantMetric(".gpu_time", units); mReporter->RegisterFyiMetric(".trial_steps", "count"); mReporter->RegisterFyiMetric(".total_steps", "count"); if (kHasATrace) { SetupATrace(); } } ANGLEPerfTest::~ANGLEPerfTest() {} void ANGLEPerfTest::run() { printf("running test name: \"%s\", backend: \"%s\", story: \"%s\"\n", mName.c_str(), mBackend.c_str(), mStory.c_str()); #if defined(ANGLE_PLATFORM_ANDROID) __android_log_print(ANDROID_LOG_INFO, "ANGLE", "running test name: \"%s\", backend: \"%s\", story: \"%s\"", mName.c_str(), mBackend.c_str(), mStory.c_str()); #endif if (mSkipTest) { GTEST_SKIP() << mSkipTestReason; // GTEST_SKIP returns. } uint32_t numTrials = OneFrame() ? 1 : gTestTrials; if (gVerboseLogging) { printf("Test Trials: %d\n", static_cast(numTrials)); } atraceCounter("TraceStage", 3); for (uint32_t trial = 0; trial < numTrials; ++trial) { runTrial(gTrialTimeSeconds, mStepsToRun, RunTrialPolicy::RunContinuously); processResults(); if (gVerboseLogging) { double trialTime = mTrialTimer.getElapsedWallClockTime(); printf("Trial %d time: %.2lf seconds.\n", trial + 1, trialTime); double secondsPerStep = trialTime / static_cast(mTrialNumStepsPerformed); double secondsPerIteration = secondsPerStep / static_cast(mIterationsPerStep); mTestTrialResults.push_back(secondsPerIteration * 1000.0); } } atraceCounter("TraceStage", 0); if (gVerboseLogging && !mTestTrialResults.empty()) { double numResults = static_cast(mTestTrialResults.size()); double mean = ComputeMean(mTestTrialResults); double variance = 0; for (double trialResult : mTestTrialResults) { double difference = trialResult - mean; variance += difference * difference; } variance /= numResults; double standardDeviation = std::sqrt(variance); double coefficientOfVariation = standardDeviation / mean; if (mean < 0.001) { printf("Mean result time: %.4lf ns.\n", mean * 1000.0); } else { printf("Mean result time: %.4lf ms.\n", mean); } printf("Coefficient of variation: %.2lf%%\n", coefficientOfVariation * 100.0); } } void ANGLEPerfTest::runTrial(double maxRunTime, int maxStepsToRun, RunTrialPolicy runPolicy) { mTrialNumStepsPerformed = 0; mRunning = true; mGPUTimeNs = 0; int stepAlignment = getStepAlignment(); mTrialTimer.start(); startTest(); int loopStepsPerformed = 0; double lastLoopWallTime = 0; while (mRunning) { // When ATrace enabled, track average frame time before the first frame of each trace loop. if (ATraceEnabled() && stepAlignment > 1 && runPolicy == RunTrialPolicy::RunContinuously && mTrialNumStepsPerformed % stepAlignment == 0) { double wallTime = mTrialTimer.getElapsedWallClockTime(); if (loopStepsPerformed > 0) // 0 at the first frame of the first loop { int frameTimeAvgUs = int(1e6 * (wallTime - lastLoopWallTime) / loopStepsPerformed); atraceCounter("TraceLoopFrameTimeAvgUs", frameTimeAvgUs); loopStepsPerformed = 0; } lastLoopWallTime = wallTime; } // Only stop on aligned steps or in a few special case modes if (mTrialNumStepsPerformed % stepAlignment == 0 || gStepsPerTrial == 1 || gRunToKeyFrame || gMaxStepsPerformed != kDefaultMaxStepsPerformed) { if (gMaxStepsPerformed > 0 && mTotalNumStepsPerformed >= gMaxStepsPerformed) { if (gVerboseLogging) { printf("Stopping test after %d total steps.\n", mTotalNumStepsPerformed); } mRunning = false; break; } if (mTrialTimer.getElapsedWallClockTime() > maxRunTime) { if (gVerboseLogging) { printf("Stopping test after %.2lf seconds.\n", mTrialTimer.getElapsedWallClockTime()); } mRunning = false; break; } if (mTrialNumStepsPerformed >= maxStepsToRun) { if (gVerboseLogging) { printf("Stopping test after %d trial steps.\n", mTrialNumStepsPerformed); } mRunning = false; break; } } step(); if (runPolicy == RunTrialPolicy::FinishEveryStep) { FinishAndCheckForContextLoss(); } if (mRunning) { mTrialNumStepsPerformed++; mTotalNumStepsPerformed++; loopStepsPerformed++; } if ((mTotalNumStepsPerformed % kNumberOfStepsPerformedToComputeGPUTime) == 0) { computeGPUTime(); } } if (runPolicy == RunTrialPolicy::RunContinuously) { atraceCounter("TraceLoopFrameTimeAvgUs", 0); } finishTest(); mTrialTimer.stop(); computeGPUTime(); } void ANGLEPerfTest::SetUp() { if (gWarmup) { atraceCounter("TraceStage", 1); // Trace tests run with glFinish for a loop (getStepAlignment == frameCount). int warmupSteps = getStepAlignment(); if (gVerboseLogging) { printf("Warmup: %d steps\n", warmupSteps); } Timer warmupTimer; warmupTimer.start(); runTrial(gTrialTimeSeconds, warmupSteps, RunTrialPolicy::FinishEveryStep); if (warmupSteps > 1) // trace tests only: getStepAlignment() is 1 otherwise { atraceCounter("TraceStage", 2); // Short traces (e.g. 10 frames) have some spikes after the first loop b/308975999 const double kMinWarmupTime = 1.5; double remainingTime = kMinWarmupTime - warmupTimer.getElapsedWallClockTime(); if (remainingTime > 0) { printf("Warmup: Looping for remaining warmup time (%.2f seconds).\n", remainingTime); runTrial(remainingTime, std::numeric_limits::max(), RunTrialPolicy::RunContinuouslyWarmup); } } if (gVerboseLogging) { printf("Warmup took %.2lf seconds.\n", warmupTimer.getElapsedWallClockTime()); } } } void ANGLEPerfTest::TearDown() {} void ANGLEPerfTest::recordIntegerMetric(const char *metric, size_t value, const std::string &units) { // Prints "RESULT ..." to stdout mReporter->AddResult(metric, value); // Saves results to file if enabled TestSuite::GetMetricWriter().writeInfo(mName, mBackend, mStory, metric, units); TestSuite::GetMetricWriter().writeIntegerValue(value); } void ANGLEPerfTest::recordDoubleMetric(const char *metric, double value, const std::string &units) { // Prints "RESULT ..." to stdout mReporter->AddResult(metric, value); // Saves results to file if enabled TestSuite::GetMetricWriter().writeInfo(mName, mBackend, mStory, metric, units); TestSuite::GetMetricWriter().writeDoubleValue(value); } void ANGLEPerfTest::addHistogramSample(const char *metric, double value, const std::string &units) { std::string measurement = mName + mBackend + metric; // Output histogram JSON set format if enabled. TestSuite::GetInstance()->addHistogramSample(measurement, mStory, value, units); } void ANGLEPerfTest::processResults() { processClockResult(".cpu_time", mTrialTimer.getElapsedCpuTime()); processClockResult(".wall_time", mTrialTimer.getElapsedWallClockTime()); if (mGPUTimeNs > 0) { processClockResult(".gpu_time", mGPUTimeNs * 1e-9); } if (gVerboseLogging) { double fps = static_cast(mTrialNumStepsPerformed * mIterationsPerStep) / mTrialTimer.getElapsedWallClockTime(); printf("Ran %0.2lf iterations per second\n", fps); } mReporter->AddResult(".trial_steps", static_cast(mTrialNumStepsPerformed)); mReporter->AddResult(".total_steps", static_cast(mTotalNumStepsPerformed)); if (!mProcessMemoryUsageKBSamples.empty()) { std::sort(mProcessMemoryUsageKBSamples.begin(), mProcessMemoryUsageKBSamples.end()); // Compute median. size_t medianIndex = mProcessMemoryUsageKBSamples.size() / 2; uint64_t medianMemoryKB = mProcessMemoryUsageKBSamples[medianIndex]; auto peakMemoryIterator = std::max_element(mProcessMemoryUsageKBSamples.begin(), mProcessMemoryUsageKBSamples.end()); uint64_t peakMemoryKB = *peakMemoryIterator; processMemoryResult(kMedianMemoryMetric, medianMemoryKB); processMemoryResult(kPeakMemoryMetric, peakMemoryKB); } for (const auto &iter : mPerfCounterInfo) { const std::string &counterName = iter.second.name; std::vector samples = iter.second.samples; // Median { size_t midpoint = samples.size() / 2; std::nth_element(samples.begin(), samples.begin() + midpoint, samples.end()); std::string medianName = "." + counterName + "_median"; recordIntegerMetric(medianName.c_str(), static_cast(samples[midpoint]), "count"); addHistogramSample(medianName.c_str(), static_cast(samples[midpoint]), "count"); } // Maximum { const auto &maxIt = std::max_element(samples.begin(), samples.end()); std::string maxName = "." + counterName + "_max"; recordIntegerMetric(maxName.c_str(), static_cast(*maxIt), "count"); addHistogramSample(maxName.c_str(), static_cast(*maxIt), "count"); } // Sum { GLuint64 sum = std::accumulate(samples.begin(), samples.end(), static_cast(0)); std::string sumName = "." + counterName + "_max"; recordIntegerMetric(sumName.c_str(), static_cast(sum), "count"); addHistogramSample(sumName.c_str(), static_cast(sum), "count"); } } } void ANGLEPerfTest::processClockResult(const char *metric, double resultSeconds) { double secondsPerStep = resultSeconds / static_cast(mTrialNumStepsPerformed); double secondsPerIteration = secondsPerStep / static_cast(mIterationsPerStep); perf_test::MetricInfo metricInfo; std::string units; bool foundMetric = mReporter->GetMetricInfo(metric, &metricInfo); if (!foundMetric) { fprintf(stderr, "Error getting metric info for %s.\n", metric); return; } units = metricInfo.units; double result; if (units == "ms") { result = secondsPerIteration * kMilliSecondsPerSecond; } else if (units == "us") { result = secondsPerIteration * kMicroSecondsPerSecond; } else { result = secondsPerIteration * kNanoSecondsPerSecond; } recordDoubleMetric(metric, result, units); addHistogramSample(metric, secondsPerIteration * kMilliSecondsPerSecond, "msBestFitFormat_smallerIsBetter"); } void ANGLEPerfTest::processMemoryResult(const char *metric, uint64_t resultKB) { perf_test::MetricInfo metricInfo; if (!mReporter->GetMetricInfo(metric, &metricInfo)) { mReporter->RegisterImportantMetric(metric, "sizeInBytes"); } recordIntegerMetric(metric, static_cast(resultKB * 1000), "sizeInBytes"); addHistogramSample(metric, static_cast(resultKB) * 1000.0, "sizeInBytes_smallerIsBetter"); } double ANGLEPerfTest::normalizedTime(size_t value) const { return static_cast(value) / static_cast(mTrialNumStepsPerformed); } int ANGLEPerfTest::getStepAlignment() const { // Default: No special alignment rules. return 1; } void ANGLEPerfTest::atraceCounter(const char *counterName, int64_t counterValue) { #if defined(ANGLE_PLATFORM_ANDROID) if (ATraceEnabled()) { gATraceSetCounter(counterName, counterValue); } #endif } RenderTestParams::RenderTestParams() { #if defined(ANGLE_DEBUG_LAYERS_ENABLED) eglParameters.debugLayersEnabled = true; #else eglParameters.debugLayersEnabled = false; #endif } std::string RenderTestParams::backend() const { std::stringstream strstr; switch (driver) { case GLESDriverType::AngleEGL: break; case GLESDriverType::AngleVulkanSecondariesEGL: strstr << "_vulkan_secondaries"; break; case GLESDriverType::SystemWGL: case GLESDriverType::SystemEGL: strstr << "_native"; break; case GLESDriverType::ZinkEGL: strstr << "_zink"; break; default: assert(0); return "_unk"; } switch (getRenderer()) { case EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE: break; case EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE: strstr << "_d3d11"; break; case EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE: strstr << "_d3d9"; break; case EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE: strstr << "_gl"; break; case EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE: strstr << "_gles"; break; case EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE: strstr << "_vulkan"; break; case EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE: strstr << "_metal"; break; default: assert(0); return "_unk"; } switch (eglParameters.deviceType) { case EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE: strstr << "_null"; break; case EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE: strstr << "_swiftshader"; break; default: break; } return strstr.str(); } std::string RenderTestParams::story() const { std::stringstream strstr; switch (surfaceType) { case SurfaceType::Window: break; case SurfaceType::WindowWithVSync: strstr << "_vsync"; break; case SurfaceType::Offscreen: strstr << "_offscreen"; break; default: UNREACHABLE(); return ""; } if (multisample) { strstr << "_" << samples << "_samples"; } return strstr.str(); } std::string RenderTestParams::backendAndStory() const { return backend() + story(); } ANGLERenderTest::ANGLERenderTest(const std::string &name, const RenderTestParams &testParams, const char *units) : ANGLEPerfTest(name, testParams.backend(), testParams.story(), OneFrame() ? 1 : testParams.iterationsPerStep, units), mTestParams(testParams), mIsTimestampQueryAvailable(false), mGLWindow(nullptr), mOSWindow(nullptr), mSwapEnabled(true) { // Force fast tests to make sure our slowest bots don't time out. if (OneFrame()) { const_cast(testParams).iterationsPerStep = 1; } // Try to ensure we don't trigger allocation during execution. mTraceEventBuffer.reserve(kInitialTraceEventBufferSize); switch (testParams.driver) { case GLESDriverType::AngleEGL: mGLWindow = EGLWindow::New(testParams.majorVersion, testParams.minorVersion); mEntryPointsLib.reset(OpenSharedLibrary(ANGLE_EGL_LIBRARY_NAME, SearchType::ModuleDir)); break; case GLESDriverType::AngleVulkanSecondariesEGL: mGLWindow = EGLWindow::New(testParams.majorVersion, testParams.minorVersion); mEntryPointsLib.reset(OpenSharedLibrary(ANGLE_VULKAN_SECONDARIES_EGL_LIBRARY_NAME, SearchType::ModuleDir)); break; case GLESDriverType::SystemEGL: #if defined(ANGLE_USE_UTIL_LOADER) && !defined(ANGLE_PLATFORM_WINDOWS) mGLWindow = EGLWindow::New(testParams.majorVersion, testParams.minorVersion); mEntryPointsLib.reset(OpenSharedLibraryWithExtension( GetNativeEGLLibraryNameWithExtension(), SearchType::SystemDir)); #else skipTest("Not implemented."); #endif // defined(ANGLE_USE_UTIL_LOADER) && !defined(ANGLE_PLATFORM_WINDOWS) break; case GLESDriverType::SystemWGL: #if defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS) mGLWindow = WGLWindow::New(testParams.majorVersion, testParams.minorVersion); mEntryPointsLib.reset(OpenSharedLibrary("opengl32", SearchType::SystemDir)); #else skipTest("WGL driver not available."); #endif // defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS) break; case GLESDriverType::ZinkEGL: mGLWindow = EGLWindow::New(testParams.majorVersion, testParams.minorVersion); mEntryPointsLib.reset( OpenSharedLibrary(ANGLE_MESA_EGL_LIBRARY_NAME, SearchType::ModuleDir)); break; default: skipTest("Error in switch."); break; } } ANGLERenderTest::~ANGLERenderTest() { OSWindow::Delete(&mOSWindow); GLWindowBase::Delete(&mGLWindow); } void ANGLERenderTest::addExtensionPrerequisite(std::string extensionName) { mExtensionPrerequisites.push_back(extensionName); } void ANGLERenderTest::addIntegerPrerequisite(GLenum target, int min) { mIntegerPrerequisites.push_back({target, min}); } void ANGLERenderTest::SetUp() { if (mSkipTest) { return; } // Set a consistent CPU core affinity and high priority. StabilizeCPUForBenchmarking(); mOSWindow = OSWindow::New(); if (!mGLWindow) { skipTest("!mGLWindow"); return; } mPlatformMethods.logError = CustomLogError; mPlatformMethods.logWarning = EmptyPlatformMethod; mPlatformMethods.logInfo = EmptyPlatformMethod; mPlatformMethods.addTraceEvent = AddPerfTraceEvent; mPlatformMethods.getTraceCategoryEnabledFlag = GetPerfTraceCategoryEnabled; mPlatformMethods.updateTraceEventDuration = UpdateTraceEventDuration; mPlatformMethods.monotonicallyIncreasingTime = MonotonicallyIncreasingTime; mPlatformMethods.context = this; if (!mOSWindow->initialize(mName, mTestParams.windowWidth, mTestParams.windowHeight)) { failTest("Failed initializing OSWindow"); return; } // Override platform method parameter. EGLPlatformParameters withMethods = mTestParams.eglParameters; withMethods.platformMethods = &mPlatformMethods; // Request a common framebuffer config mConfigParams.redBits = 8; mConfigParams.greenBits = 8; mConfigParams.blueBits = 8; mConfigParams.alphaBits = 8; mConfigParams.depthBits = 24; mConfigParams.stencilBits = 8; mConfigParams.colorSpace = mTestParams.colorSpace; mConfigParams.multisample = mTestParams.multisample; mConfigParams.samples = mTestParams.samples; if (mTestParams.surfaceType != SurfaceType::WindowWithVSync) { mConfigParams.swapInterval = 0; } if (gPrintExtensionsToFile != nullptr || gRequestedExtensions != nullptr) { mConfigParams.extensionsEnabled = false; } GLWindowResult res = mGLWindow->initializeGLWithResult( mOSWindow, mEntryPointsLib.get(), mTestParams.driver, withMethods, mConfigParams); switch (res) { case GLWindowResult::NoColorspaceSupport: skipTest("Missing support for color spaces."); return; case GLWindowResult::Error: failTest("Failed initializing GL Window"); return; default: break; } if (gPrintExtensionsToFile) { std::ofstream fout(gPrintExtensionsToFile); if (fout.is_open()) { int numExtensions = 0; glGetIntegerv(GL_NUM_REQUESTABLE_EXTENSIONS_ANGLE, &numExtensions); for (int ext = 0; ext < numExtensions; ext++) { fout << glGetStringi(GL_REQUESTABLE_EXTENSIONS_ANGLE, ext) << std::endl; } fout.close(); std::stringstream statusString; statusString << "Wrote out to file: " << gPrintExtensionsToFile; skipTest(statusString.str()); } else { std::stringstream failStr; failStr << "Failed to open file: " << gPrintExtensionsToFile; failTest(failStr.str()); } return; } if (gRequestedExtensions != nullptr) { std::istringstream ss{gRequestedExtensions}; std::string ext; while (std::getline(ss, ext, ' ')) { glRequestExtensionANGLE(ext.c_str()); } } // Disable vsync (if not done by the window init). if (mTestParams.surfaceType != SurfaceType::WindowWithVSync) { if (!mGLWindow->setSwapInterval(0)) { failTest("Failed setting swap interval"); return; } } if (mTestParams.trackGpuTime) { mIsTimestampQueryAvailable = EnsureGLExtensionEnabled("GL_EXT_disjoint_timer_query"); } skipTestIfMissingExtensionPrerequisites(); skipTestIfFailsIntegerPrerequisite(); if (mSkipTest) { GTEST_SKIP() << mSkipTestReason; // GTEST_SKIP returns. } #if defined(ANGLE_ENABLE_ASSERTS) if (IsGLExtensionEnabled("GL_KHR_debug") && mEnableDebugCallback) { EnableDebugCallback(&PerfTestDebugCallback, this); } #endif initializeBenchmark(); if (mSkipTest) { GTEST_SKIP() << mSkipTestReason; // GTEST_SKIP returns. } if (mTestParams.iterationsPerStep == 0) { failTest("Please initialize 'iterationsPerStep'."); return; } if (gVerboseLogging) { printf("GL_RENDERER: %s\n", glGetString(GL_RENDERER)); printf("GL_VERSION: %s\n", glGetString(GL_VERSION)); } mTestTrialResults.reserve(gTestTrials); // Runs warmup if enabled ANGLEPerfTest::SetUp(); initPerfCounters(); } void ANGLERenderTest::TearDown() { ASSERT(mTimestampQueries.empty()); if (!mPerfCounterInfo.empty()) { glDeletePerfMonitorsAMD(1, &mPerfMonitor); mPerfMonitor = 0; } if (!mSkipTest) { destroyBenchmark(); } if (mGLWindow) { mGLWindow->destroyGL(); mGLWindow = nullptr; } if (mOSWindow) { mOSWindow->destroy(); mOSWindow = nullptr; } // Dump trace events to json file. if (gEnableTrace) { DumpTraceEventsToJSONFile(mTraceEventBuffer, gTraceFile); } ANGLEPerfTest::TearDown(); } void ANGLERenderTest::initPerfCounters() { if (!gPerfCounters) { return; } if (!IsGLExtensionEnabled(kPerfMonitorExtensionName)) { fprintf(stderr, "Cannot report perf metrics because %s is not available.\n", kPerfMonitorExtensionName); return; } CounterNameToIndexMap indexMap = BuildCounterNameToIndexMap(); std::vector counters = angle::SplitString(gPerfCounters, ":", angle::WhitespaceHandling::TRIM_WHITESPACE, angle::SplitResult::SPLIT_WANT_NONEMPTY); for (const std::string &counter : counters) { bool found = false; for (const auto &indexMapIter : indexMap) { const std::string &indexMapName = indexMapIter.first; if (NamesMatchWithWildcard(counter.c_str(), indexMapName.c_str())) { { std::stringstream medianStr; medianStr << '.' << indexMapName << "_median"; std::string medianName = medianStr.str(); mReporter->RegisterImportantMetric(medianName, "count"); } { std::stringstream maxStr; maxStr << '.' << indexMapName << "_max"; std::string maxName = maxStr.str(); mReporter->RegisterImportantMetric(maxName, "count"); } { std::stringstream sumStr; sumStr << '.' << indexMapName << "_sum"; std::string sumName = sumStr.str(); mReporter->RegisterImportantMetric(sumName, "count"); } GLuint index = indexMapIter.second; mPerfCounterInfo[index] = {indexMapName, {}}; found = true; } } if (!found) { fprintf(stderr, "'%s' does not match any available perf counters.\n", counter.c_str()); } } if (!mPerfCounterInfo.empty()) { glGenPerfMonitorsAMD(1, &mPerfMonitor); // Note: technically, glSelectPerfMonitorCountersAMD should be used to select the counters, // but currently ANGLE always captures all counters. } } void ANGLERenderTest::updatePerfCounters() { if (mPerfCounterInfo.empty()) { return; } std::vector perfData = GetPerfMonitorTriplets(); ASSERT(!perfData.empty()); for (auto &iter : mPerfCounterInfo) { uint32_t counter = iter.first; std::vector &samples = iter.second.samples; samples.push_back(perfData[counter].value); } } void ANGLERenderTest::beginInternalTraceEvent(const char *name) { if (gEnableTrace) { mTraceEventBuffer.emplace_back(TRACE_EVENT_PHASE_BEGIN, gTraceCategories[0].name, name, MonotonicallyIncreasingTime(&mPlatformMethods), getCurrentThreadSerial()); } } void ANGLERenderTest::endInternalTraceEvent(const char *name) { if (gEnableTrace) { mTraceEventBuffer.emplace_back(TRACE_EVENT_PHASE_END, gTraceCategories[0].name, name, MonotonicallyIncreasingTime(&mPlatformMethods), getCurrentThreadSerial()); } } void ANGLERenderTest::beginGLTraceEvent(const char *name, double hostTimeSec) { if (gEnableTrace) { mTraceEventBuffer.emplace_back(TRACE_EVENT_PHASE_BEGIN, gTraceCategories[1].name, name, hostTimeSec, getCurrentThreadSerial()); } } void ANGLERenderTest::endGLTraceEvent(const char *name, double hostTimeSec) { if (gEnableTrace) { mTraceEventBuffer.emplace_back(TRACE_EVENT_PHASE_END, gTraceCategories[1].name, name, hostTimeSec, getCurrentThreadSerial()); } } void ANGLERenderTest::step() { beginInternalTraceEvent("step"); // Clear events that the application did not process from this frame Event event; bool closed = false; while (popEvent(&event)) { // If the application did not catch a close event, close now if (event.Type == Event::EVENT_CLOSED) { closed = true; } } if (closed) { abortTest(); } else { drawBenchmark(); // Swap is needed so that the GPU driver will occasionally flush its // internal command queue to the GPU. This is enabled for null back-end // devices because some back-ends (e.g. Vulkan) also accumulate internal // command queues. if (mSwapEnabled) { updatePerfCounters(); mGLWindow->swap(); } mOSWindow->messageLoop(); #if defined(ANGLE_ENABLE_ASSERTS) if (!gRetraceMode) { EXPECT_EQ(static_cast(GL_NO_ERROR), glGetError()); } #endif // defined(ANGLE_ENABLE_ASSERTS) // Sample system memory uint64_t processMemoryUsageKB = GetProcessMemoryUsageKB(); if (processMemoryUsageKB) { mProcessMemoryUsageKBSamples.push_back(processMemoryUsageKB); } } endInternalTraceEvent("step"); } void ANGLERenderTest::startGpuTimer() { if (mTestParams.trackGpuTime && mIsTimestampQueryAvailable) { glGenQueriesEXT(1, &mCurrentTimestampBeginQuery); glQueryCounterEXT(mCurrentTimestampBeginQuery, GL_TIMESTAMP_EXT); } } void ANGLERenderTest::stopGpuTimer() { if (mTestParams.trackGpuTime && mIsTimestampQueryAvailable) { GLuint endQuery = 0; glGenQueriesEXT(1, &endQuery); glQueryCounterEXT(endQuery, GL_TIMESTAMP_EXT); mTimestampQueries.push({mCurrentTimestampBeginQuery, endQuery}); } } void ANGLERenderTest::computeGPUTime() { if (mTestParams.trackGpuTime && mIsTimestampQueryAvailable) { while (!mTimestampQueries.empty()) { const TimestampSample &sample = mTimestampQueries.front(); GLuint available = GL_FALSE; glGetQueryObjectuivEXT(sample.endQuery, GL_QUERY_RESULT_AVAILABLE_EXT, &available); if (available != GL_TRUE) { // query is not completed yet, bail out break; } // frame's begin query must also completed. glGetQueryObjectuivEXT(sample.beginQuery, GL_QUERY_RESULT_AVAILABLE_EXT, &available); ASSERT(available == GL_TRUE); // Retrieve query result uint64_t beginGLTimeNs = 0; uint64_t endGLTimeNs = 0; glGetQueryObjectui64vEXT(sample.beginQuery, GL_QUERY_RESULT_EXT, &beginGLTimeNs); glGetQueryObjectui64vEXT(sample.endQuery, GL_QUERY_RESULT_EXT, &endGLTimeNs); glDeleteQueriesEXT(1, &sample.beginQuery); glDeleteQueriesEXT(1, &sample.endQuery); mTimestampQueries.pop(); // compute GPU time mGPUTimeNs += endGLTimeNs - beginGLTimeNs; } } } void ANGLERenderTest::startTest() { if (!mPerfCounterInfo.empty()) { glBeginPerfMonitorAMD(mPerfMonitor); } } void ANGLERenderTest::finishTest() { if (mTestParams.eglParameters.deviceType != EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE && !gNoFinish && !gRetraceMode) { FinishAndCheckForContextLoss(); } if (!mPerfCounterInfo.empty()) { glEndPerfMonitorAMD(mPerfMonitor); } } bool ANGLERenderTest::popEvent(Event *event) { return mOSWindow->popEvent(event); } OSWindow *ANGLERenderTest::getWindow() { return mOSWindow; } GLWindowBase *ANGLERenderTest::getGLWindow() { return mGLWindow; } void ANGLERenderTest::skipTestIfMissingExtensionPrerequisites() { for (std::string extension : mExtensionPrerequisites) { if (!CheckExtensionExists(reinterpret_cast(glGetString(GL_EXTENSIONS)), extension)) { skipTest(std::string("Test skipped due to missing extension: ") + extension); return; } } } void ANGLERenderTest::skipTestIfFailsIntegerPrerequisite() { for (const auto [target, minRequired] : mIntegerPrerequisites) { GLint driverValue; glGetIntegerv(target, &driverValue); if (static_cast(driverValue) < minRequired) { std::stringstream ss; ss << "Test skipped due to value (" << std::to_string(static_cast(driverValue)) << ") being less than the prerequisite minimum (" << std::to_string(minRequired) << ") for GL constant " << gl::GLenumToString(gl::GLESEnum::AllEnums, target); skipTest(ss.str()); } } } void ANGLERenderTest::setWebGLCompatibilityEnabled(bool webglCompatibility) { mConfigParams.webGLCompatibility = webglCompatibility; } void ANGLERenderTest::setRobustResourceInit(bool enabled) { mConfigParams.robustResourceInit = enabled; } std::vector &ANGLERenderTest::getTraceEventBuffer() { return mTraceEventBuffer; } void ANGLERenderTest::onErrorMessage(const char *errorMessage) { abortTest(); std::ostringstream err; err << "Failing test because of unexpected error:\n" << errorMessage << "\n"; failTest(err.str()); } uint32_t ANGLERenderTest::getCurrentThreadSerial() { uint64_t id = angle::GetCurrentThreadUniqueId(); for (uint32_t serial = 0; serial < static_cast(mThreadIDs.size()); ++serial) { if (mThreadIDs[serial] == id) { return serial + 1; } } mThreadIDs.push_back(id); return static_cast(mThreadIDs.size()); } namespace angle { double GetHostTimeSeconds() { // Move the time origin to the first call to this function, to avoid generating unnecessarily // large timestamps. static double origin = GetCurrentSystemTime(); return GetCurrentSystemTime() - origin; } } // namespace angle