| // |
| // 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 <android/log.h> |
| # include <dlfcn.h> |
| #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 <cassert> |
| #include <cmath> |
| #include <fstream> |
| #include <iostream> |
| #include <numeric> |
| #include <sstream> |
| #include <string> |
| |
| #include <rapidjson/document.h> |
| #include <rapidjson/filewritestream.h> |
| #include <rapidjson/istreamwrapper.h> |
| #include <rapidjson/prettywriter.h> |
| |
| #if defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS) |
| # include "util/windows/WGLWindow.h" |
| #endif // defined(ANGLE_USE_UTIL_LOADER) &&defined(ANGLE_PLATFORM_WINDOWS) |
| |
| 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<ANGLERenderTest *>(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<const TraceCategory *>(categoryEnabledFlag); |
| |
| ANGLERenderTest *renderTest = static_cast<ANGLERenderTest *>(platform->context); |
| |
| std::lock_guard<std::mutex> lock(renderTest->getTraceEventMutex()); |
| |
| uint32_t tid = renderTest->getCurrentThreadSerial(); |
| |
| std::vector<TraceEvent> &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<char> writeBuffer(kBufferSize); |
| js::FileWriteStream os(fp, writeBuffer.data(), kBufferSize); |
| js::PrettyWriter<js::FileWriteStream> writer(os); |
| if (!doc->Accept(writer)) |
| { |
| fclose(fp); |
| return false; |
| } |
| fclose(fp); |
| return true; |
| } |
| |
| void DumpTraceEventsToJSONFile(const std::vector<TraceEvent> &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<uint64_t>(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<ANGLERenderTest *>(reinterpret_cast<const ANGLERenderTest *>(userParam)); |
| renderTest->onErrorMessage(message); |
| } |
| |
| double ComputeMean(const std::vector<double> &values) |
| { |
| double sum = std::accumulate(values.begin(), values.end(), 0.0); |
| |
| double mean = sum / static_cast<double>(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(); |
| } |
| |
| void ATraceCounter(const char *counterName, int64_t counterValue) |
| { |
| if (ATraceEnabled()) |
| { |
| gATraceSetCounter(counterName, counterValue); |
| } |
| } |
| #else |
| constexpr bool kHasATrace = false; |
| void SetupATrace() {} |
| bool ATraceEnabled() |
| { |
| return false; |
| } |
| void ATraceCounter(const char *counterName, int64_t counterValue) {} |
| #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<perf_test::PerfResultReporter>(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<int>(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<double>(mTrialNumStepsPerformed); |
| double secondsPerIteration = secondsPerStep / static_cast<double>(mIterationsPerStep); |
| mTestTrialResults.push_back(secondsPerIteration * 1000.0); |
| } |
| } |
| |
| ATraceCounter("TraceStage", 0); |
| |
| if (gVerboseLogging && !mTestTrialResults.empty()) |
| { |
| double numResults = static_cast<double>(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<int>::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<double>(mTrialNumStepsPerformed * mIterationsPerStep) / |
| mTrialTimer.getElapsedWallClockTime(); |
| printf("Ran %0.2lf iterations per second\n", fps); |
| } |
| |
| mReporter->AddResult(".trial_steps", static_cast<size_t>(mTrialNumStepsPerformed)); |
| mReporter->AddResult(".total_steps", static_cast<size_t>(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<GLuint64> 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<size_t>(samples[midpoint]), |
| "count"); |
| addHistogramSample(medianName.c_str(), static_cast<double>(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<size_t>(*maxIt), "count"); |
| addHistogramSample(maxName.c_str(), static_cast<double>(*maxIt), "count"); |
| } |
| |
| // Sum |
| { |
| GLuint64 sum = |
| std::accumulate(samples.begin(), samples.end(), static_cast<GLuint64>(0)); |
| |
| std::string sumName = "." + counterName + "_max"; |
| recordIntegerMetric(sumName.c_str(), static_cast<size_t>(sum), "count"); |
| addHistogramSample(sumName.c_str(), static_cast<double>(sum), "count"); |
| } |
| } |
| } |
| |
| void ANGLEPerfTest::processClockResult(const char *metric, double resultSeconds) |
| { |
| double secondsPerStep = resultSeconds / static_cast<double>(mTrialNumStepsPerformed); |
| double secondsPerIteration = secondsPerStep / static_cast<double>(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<size_t>(resultKB * 1000), "sizeInBytes"); |
| addHistogramSample(metric, static_cast<double>(resultKB) * 1000.0, |
| "sizeInBytes_smallerIsBetter"); |
| } |
| |
| double ANGLEPerfTest::normalizedTime(size_t value) const |
| { |
| return static_cast<double>(value) / static_cast<double>(mTrialNumStepsPerformed); |
| } |
| |
| int ANGLEPerfTest::getStepAlignment() const |
| { |
| // Default: No special alignment rules. |
| return 1; |
| } |
| |
| 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<RenderTestParams &>(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.clientType, testParams.majorVersion, |
| testParams.minorVersion, testParams.profileMask); |
| mEntryPointsLib.reset(OpenSharedLibrary(ANGLE_EGL_LIBRARY_NAME, SearchType::ModuleDir)); |
| break; |
| case GLESDriverType::AngleVulkanSecondariesEGL: |
| mGLWindow = EGLWindow::New(testParams.clientType, testParams.majorVersion, |
| testParams.minorVersion, testParams.profileMask); |
| 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.clientType, testParams.majorVersion, |
| testParams.minorVersion, testParams.profileMask); |
| 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.clientType, testParams.majorVersion, |
| testParams.minorVersion, testParams.profileMask); |
| 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.clientType, testParams.majorVersion, |
| testParams.minorVersion, testParams.profileMask); |
| 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<std::string> 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<PerfMonitorTriplet> perfData = GetPerfMonitorTriplets(); |
| ASSERT(!perfData.empty()); |
| |
| for (auto &iter : mPerfCounterInfo) |
| { |
| uint32_t counter = iter.first; |
| std::vector<GLuint64> &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<GLenum>(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<const char *>(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<int>(driverValue) < minRequired) |
| { |
| std::stringstream ss; |
| ss << "Test skipped due to value (" << std::to_string(static_cast<int>(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<TraceEvent> &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<uint32_t>(mThreadIDs.size()); ++serial) |
| { |
| if (mThreadIDs[serial] == id) |
| { |
| return serial + 1; |
| } |
| } |
| |
| mThreadIDs.push_back(id); |
| return static_cast<uint32_t>(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 |