1 //
2 // Copyright 2014 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // ANGLEPerfTests:
7 // Base class for google test performance tests
8 //
9
10 #include "ANGLEPerfTest.h"
11
12 #if defined(ANGLE_PLATFORM_ANDROID)
13 # include <android/log.h>
14 #endif
15 #include "ANGLEPerfTestArgs.h"
16 #include "common/base/anglebase/trace_event/trace_event.h"
17 #include "common/debug.h"
18 #include "common/gl_enum_utils.h"
19 #include "common/mathutil.h"
20 #include "common/platform.h"
21 #include "common/string_utils.h"
22 #include "common/system_utils.h"
23 #include "common/utilities.h"
24 #include "test_utils/runner/TestSuite.h"
25 #include "third_party/perf/perf_test.h"
26 #include "util/shader_utils.h"
27 #include "util/test_utils.h"
28
29 #include <cassert>
30 #include <cmath>
31 #include <fstream>
32 #include <iostream>
33 #include <numeric>
34 #include <sstream>
35 #include <string>
36
37 #include <rapidjson/document.h>
38 #include <rapidjson/filewritestream.h>
39 #include <rapidjson/istreamwrapper.h>
40 #include <rapidjson/prettywriter.h>
41
42 #if defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
43 # include "util/windows/WGLWindow.h"
44 #endif // defined(ANGLE_USE_UTIL_LOADER) &&defined(ANGLE_PLATFORM_WINDOWS)
45
46 using namespace angle;
47 namespace js = rapidjson;
48
49 namespace
50 {
51 constexpr size_t kInitialTraceEventBufferSize = 50000;
52 constexpr double kMilliSecondsPerSecond = 1e3;
53 constexpr double kMicroSecondsPerSecond = 1e6;
54 constexpr double kNanoSecondsPerSecond = 1e9;
55 constexpr size_t kNumberOfStepsPerformedToComputeGPUTime = 16;
56 constexpr char kPeakMemoryMetric[] = ".memory_max";
57 constexpr char kMedianMemoryMetric[] = ".memory_median";
58
59 struct TraceCategory
60 {
61 unsigned char enabled;
62 const char *name;
63 };
64
65 constexpr TraceCategory gTraceCategories[2] = {
66 {1, "gpu.angle"},
67 {1, "gpu.angle.gpu"},
68 };
69
EmptyPlatformMethod(PlatformMethods *,const char *)70 void EmptyPlatformMethod(PlatformMethods *, const char *) {}
71
CustomLogError(PlatformMethods * platform,const char * errorMessage)72 void CustomLogError(PlatformMethods *platform, const char *errorMessage)
73 {
74 auto *angleRenderTest = static_cast<ANGLERenderTest *>(platform->context);
75 angleRenderTest->onErrorMessage(errorMessage);
76 }
77
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)78 TraceEventHandle AddPerfTraceEvent(PlatformMethods *platform,
79 char phase,
80 const unsigned char *categoryEnabledFlag,
81 const char *name,
82 unsigned long long id,
83 double timestamp,
84 int numArgs,
85 const char **argNames,
86 const unsigned char *argTypes,
87 const unsigned long long *argValues,
88 unsigned char flags)
89 {
90 if (!gEnableTrace)
91 return 0;
92
93 // Discover the category name based on categoryEnabledFlag. This flag comes from the first
94 // parameter of TraceCategory, and corresponds to one of the entries in gTraceCategories.
95 static_assert(offsetof(TraceCategory, enabled) == 0,
96 "|enabled| must be the first field of the TraceCategory class.");
97 const TraceCategory *category = reinterpret_cast<const TraceCategory *>(categoryEnabledFlag);
98
99 ANGLERenderTest *renderTest = static_cast<ANGLERenderTest *>(platform->context);
100
101 std::lock_guard<std::mutex> lock(renderTest->getTraceEventMutex());
102
103 uint32_t tid = renderTest->getCurrentThreadSerial();
104
105 std::vector<TraceEvent> &buffer = renderTest->getTraceEventBuffer();
106 buffer.emplace_back(phase, category->name, name, timestamp, tid);
107 return buffer.size();
108 }
109
GetPerfTraceCategoryEnabled(PlatformMethods * platform,const char * categoryName)110 const unsigned char *GetPerfTraceCategoryEnabled(PlatformMethods *platform,
111 const char *categoryName)
112 {
113 if (gEnableTrace)
114 {
115 for (const TraceCategory &category : gTraceCategories)
116 {
117 if (strcmp(category.name, categoryName) == 0)
118 {
119 return &category.enabled;
120 }
121 }
122 }
123
124 constexpr static unsigned char kZero = 0;
125 return &kZero;
126 }
127
UpdateTraceEventDuration(PlatformMethods * platform,const unsigned char * categoryEnabledFlag,const char * name,TraceEventHandle eventHandle)128 void UpdateTraceEventDuration(PlatformMethods *platform,
129 const unsigned char *categoryEnabledFlag,
130 const char *name,
131 TraceEventHandle eventHandle)
132 {
133 // Not implemented.
134 }
135
MonotonicallyIncreasingTime(PlatformMethods * platform)136 double MonotonicallyIncreasingTime(PlatformMethods *platform)
137 {
138 return GetHostTimeSeconds();
139 }
140
WriteJsonFile(const std::string & outputFile,js::Document * doc)141 bool WriteJsonFile(const std::string &outputFile, js::Document *doc)
142 {
143 FILE *fp = fopen(outputFile.c_str(), "w");
144 if (!fp)
145 {
146 return false;
147 }
148
149 constexpr size_t kBufferSize = 0xFFFF;
150 std::vector<char> writeBuffer(kBufferSize);
151 js::FileWriteStream os(fp, writeBuffer.data(), kBufferSize);
152 js::PrettyWriter<js::FileWriteStream> writer(os);
153 if (!doc->Accept(writer))
154 {
155 fclose(fp);
156 return false;
157 }
158 fclose(fp);
159 return true;
160 }
161
DumpTraceEventsToJSONFile(const std::vector<TraceEvent> & traceEvents,const char * outputFileName)162 void DumpTraceEventsToJSONFile(const std::vector<TraceEvent> &traceEvents,
163 const char *outputFileName)
164 {
165 js::Document doc(js::kObjectType);
166 js::Document::AllocatorType &allocator = doc.GetAllocator();
167
168 js::Value events(js::kArrayType);
169
170 for (const TraceEvent &traceEvent : traceEvents)
171 {
172 js::Value value(js::kObjectType);
173
174 const uint64_t microseconds = static_cast<uint64_t>(traceEvent.timestamp * 1000.0 * 1000.0);
175
176 js::Document::StringRefType eventName(traceEvent.name);
177 js::Document::StringRefType categoryName(traceEvent.categoryName);
178 js::Document::StringRefType pidName(
179 strcmp(traceEvent.categoryName, "gpu.angle.gpu") == 0 ? "GPU" : "ANGLE");
180
181 value.AddMember("name", eventName, allocator);
182 value.AddMember("cat", categoryName, allocator);
183 value.AddMember("ph", std::string(1, traceEvent.phase), allocator);
184 value.AddMember("ts", microseconds, allocator);
185 value.AddMember("pid", pidName, allocator);
186 value.AddMember("tid", traceEvent.tid, allocator);
187
188 events.PushBack(value, allocator);
189 }
190
191 doc.AddMember("traceEvents", events, allocator);
192
193 if (WriteJsonFile(outputFileName, &doc))
194 {
195 printf("Wrote trace file to %s\n", outputFileName);
196 }
197 else
198 {
199 printf("Error writing trace file to %s\n", outputFileName);
200 }
201 }
202
PerfTestDebugCallback(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar * message,const void * userParam)203 [[maybe_unused]] void KHRONOS_APIENTRY PerfTestDebugCallback(GLenum source,
204 GLenum type,
205 GLuint id,
206 GLenum severity,
207 GLsizei length,
208 const GLchar *message,
209 const void *userParam)
210 {
211 // Early exit on non-errors.
212 if (type != GL_DEBUG_TYPE_ERROR || !userParam)
213 {
214 return;
215 }
216
217 ANGLERenderTest *renderTest =
218 const_cast<ANGLERenderTest *>(reinterpret_cast<const ANGLERenderTest *>(userParam));
219 renderTest->onErrorMessage(message);
220 }
221
ComputeMean(const std::vector<double> & values)222 double ComputeMean(const std::vector<double> &values)
223 {
224 double sum = std::accumulate(values.begin(), values.end(), 0.0);
225
226 double mean = sum / static_cast<double>(values.size());
227 return mean;
228 }
229
FinishAndCheckForContextLoss()230 void FinishAndCheckForContextLoss()
231 {
232 glFinish();
233 if (glGetError() == GL_CONTEXT_LOST)
234 {
235 FAIL() << "Context lost";
236 }
237 }
238 } // anonymous namespace
239
TraceEvent(char phaseIn,const char * categoryNameIn,const char * nameIn,double timestampIn,uint32_t tidIn)240 TraceEvent::TraceEvent(char phaseIn,
241 const char *categoryNameIn,
242 const char *nameIn,
243 double timestampIn,
244 uint32_t tidIn)
245 : phase(phaseIn), categoryName(categoryNameIn), name{}, timestamp(timestampIn), tid(tidIn)
246 {
247 ASSERT(strlen(nameIn) < kMaxNameLen);
248 strcpy(name, nameIn);
249 }
250
ANGLEPerfTest(const std::string & name,const std::string & backend,const std::string & story,unsigned int iterationsPerStep,const char * units)251 ANGLEPerfTest::ANGLEPerfTest(const std::string &name,
252 const std::string &backend,
253 const std::string &story,
254 unsigned int iterationsPerStep,
255 const char *units)
256 : mName(name),
257 mBackend(backend),
258 mStory(story),
259 mGPUTimeNs(0),
260 mSkipTest(false),
261 mStepsToRun(std::max(gStepsPerTrial, gMaxStepsPerformed)),
262 mTrialNumStepsPerformed(0),
263 mTotalNumStepsPerformed(0),
264 mIterationsPerStep(iterationsPerStep),
265 mRunning(true)
266 {
267 if (mStory == "")
268 {
269 mStory = "baseline_story";
270 }
271 if (mStory[0] == '_')
272 {
273 mStory = mStory.substr(1);
274 }
275 mReporter = std::make_unique<perf_test::PerfResultReporter>(mName + mBackend, mStory);
276 mReporter->RegisterImportantMetric(".wall_time", units);
277 mReporter->RegisterImportantMetric(".cpu_time", units);
278 mReporter->RegisterImportantMetric(".gpu_time", units);
279 mReporter->RegisterFyiMetric(".trial_steps", "count");
280 mReporter->RegisterFyiMetric(".total_steps", "count");
281 }
282
~ANGLEPerfTest()283 ANGLEPerfTest::~ANGLEPerfTest() {}
284
run()285 void ANGLEPerfTest::run()
286 {
287 printf("running test name: \"%s\", backend: \"%s\", story: \"%s\"\n", mName.c_str(),
288 mBackend.c_str(), mStory.c_str());
289 #if defined(ANGLE_PLATFORM_ANDROID)
290 __android_log_print(ANDROID_LOG_INFO, "ANGLE",
291 "running test name: \"%s\", backend: \"%s\", story: \"%s\"", mName.c_str(),
292 mBackend.c_str(), mStory.c_str());
293 #endif
294 if (mSkipTest)
295 {
296 GTEST_SKIP() << mSkipTestReason;
297 // GTEST_SKIP returns.
298 }
299
300 uint32_t numTrials = OneFrame() ? 1 : gTestTrials;
301 if (gVerboseLogging)
302 {
303 printf("Test Trials: %d\n", static_cast<int>(numTrials));
304 }
305
306 for (uint32_t trial = 0; trial < numTrials; ++trial)
307 {
308 runTrial(gTrialTimeSeconds, mStepsToRun, RunTrialPolicy::RunContinuously);
309 processResults();
310 if (gVerboseLogging)
311 {
312 double trialTime = mTrialTimer.getElapsedWallClockTime();
313 printf("Trial %d time: %.2lf seconds.\n", trial + 1, trialTime);
314
315 double secondsPerStep = trialTime / static_cast<double>(mTrialNumStepsPerformed);
316 double secondsPerIteration = secondsPerStep / static_cast<double>(mIterationsPerStep);
317 mTestTrialResults.push_back(secondsPerIteration * 1000.0);
318 }
319 }
320
321 if (gVerboseLogging && !mTestTrialResults.empty())
322 {
323 double numResults = static_cast<double>(mTestTrialResults.size());
324 double mean = ComputeMean(mTestTrialResults);
325
326 double variance = 0;
327 for (double trialResult : mTestTrialResults)
328 {
329 double difference = trialResult - mean;
330 variance += difference * difference;
331 }
332 variance /= numResults;
333
334 double standardDeviation = std::sqrt(variance);
335 double coefficientOfVariation = standardDeviation / mean;
336
337 if (mean < 0.001)
338 {
339 printf("Mean result time: %.4lf ns.\n", mean * 1000.0);
340 }
341 else
342 {
343 printf("Mean result time: %.4lf ms.\n", mean);
344 }
345 printf("Coefficient of variation: %.2lf%%\n", coefficientOfVariation * 100.0);
346 }
347 }
348
runTrial(double maxRunTime,int maxStepsToRun,RunTrialPolicy runPolicy)349 void ANGLEPerfTest::runTrial(double maxRunTime, int maxStepsToRun, RunTrialPolicy runPolicy)
350 {
351 mTrialNumStepsPerformed = 0;
352 mRunning = true;
353 mGPUTimeNs = 0;
354 int stepAlignment = getStepAlignment();
355 mTrialTimer.start();
356 startTest();
357
358 while (mRunning)
359 {
360 // Only stop on aligned steps or in a few special case modes
361 if (mTrialNumStepsPerformed % stepAlignment == 0 || gStepsPerTrial == 1 || gRunToKeyFrame ||
362 gMaxStepsPerformed != kDefaultMaxStepsPerformed)
363 {
364 if (gMaxStepsPerformed > 0 && mTotalNumStepsPerformed >= gMaxStepsPerformed)
365 {
366 if (gVerboseLogging)
367 {
368 printf("Stopping test after %d total steps.\n", mTotalNumStepsPerformed);
369 }
370 mRunning = false;
371 break;
372 }
373 if (mTrialTimer.getElapsedWallClockTime() > maxRunTime)
374 {
375 if (gVerboseLogging)
376 {
377 printf("Stopping test after %.2lf seconds.\n",
378 mTrialTimer.getElapsedWallClockTime());
379 }
380 mRunning = false;
381 break;
382 }
383 if (mTrialNumStepsPerformed >= maxStepsToRun)
384 {
385 if (gVerboseLogging)
386 {
387 printf("Stopping test after %d trial steps.\n", mTrialNumStepsPerformed);
388 }
389 mRunning = false;
390 break;
391 }
392 }
393
394 step();
395
396 if (runPolicy == RunTrialPolicy::FinishEveryStep)
397 {
398 FinishAndCheckForContextLoss();
399 }
400
401 if (mRunning)
402 {
403 mTrialNumStepsPerformed++;
404 mTotalNumStepsPerformed++;
405 }
406
407 if ((mTotalNumStepsPerformed % kNumberOfStepsPerformedToComputeGPUTime) == 0)
408 {
409 computeGPUTime();
410 }
411 }
412 finishTest();
413 mTrialTimer.stop();
414 computeGPUTime();
415 }
416
SetUp()417 void ANGLEPerfTest::SetUp()
418 {
419 if (gWarmup)
420 {
421 // Trace tests run with glFinish for a loop (getStepAlignment == frameCount).
422 int warmupSteps = getStepAlignment();
423 if (gVerboseLogging)
424 {
425 printf("Warmup: %d steps\n", warmupSteps);
426 }
427
428 Timer warmupTimer;
429 warmupTimer.start();
430
431 runTrial(gTrialTimeSeconds, warmupSteps, RunTrialPolicy::FinishEveryStep);
432
433 if (warmupSteps > 1) // trace tests only: getStepAlignment() is 1 otherwise
434 {
435 // Short traces (e.g. 10 frames) have some spikes after the first loop b/308975999
436 const double kMinWarmupTime = 1.5;
437 double remainingTime = kMinWarmupTime - warmupTimer.getElapsedWallClockTime();
438 if (remainingTime > 0)
439 {
440 printf("Warmup: Looping for remaining warmup time (%.2f seconds).\n",
441 remainingTime);
442 runTrial(remainingTime, std::numeric_limits<int>::max(),
443 RunTrialPolicy::RunContinuously);
444 }
445 }
446
447 if (gVerboseLogging)
448 {
449 printf("Warmup took %.2lf seconds.\n", warmupTimer.getElapsedWallClockTime());
450 }
451 }
452 }
453
TearDown()454 void ANGLEPerfTest::TearDown() {}
455
recordIntegerMetric(const char * metric,size_t value,const std::string & units)456 void ANGLEPerfTest::recordIntegerMetric(const char *metric, size_t value, const std::string &units)
457 {
458 // Prints "RESULT ..." to stdout
459 mReporter->AddResult(metric, value);
460
461 // Saves results to file if enabled
462 TestSuite::GetMetricWriter().writeInfo(mName, mBackend, mStory, metric, units);
463 TestSuite::GetMetricWriter().writeIntegerValue(value);
464 }
465
recordDoubleMetric(const char * metric,double value,const std::string & units)466 void ANGLEPerfTest::recordDoubleMetric(const char *metric, double value, const std::string &units)
467 {
468 // Prints "RESULT ..." to stdout
469 mReporter->AddResult(metric, value);
470
471 // Saves results to file if enabled
472 TestSuite::GetMetricWriter().writeInfo(mName, mBackend, mStory, metric, units);
473 TestSuite::GetMetricWriter().writeDoubleValue(value);
474 }
475
addHistogramSample(const char * metric,double value,const std::string & units)476 void ANGLEPerfTest::addHistogramSample(const char *metric, double value, const std::string &units)
477 {
478 std::string measurement = mName + mBackend + metric;
479 // Output histogram JSON set format if enabled.
480 TestSuite::GetInstance()->addHistogramSample(measurement, mStory, value, units);
481 }
482
processResults()483 void ANGLEPerfTest::processResults()
484 {
485 processClockResult(".cpu_time", mTrialTimer.getElapsedCpuTime());
486 processClockResult(".wall_time", mTrialTimer.getElapsedWallClockTime());
487
488 if (mGPUTimeNs > 0)
489 {
490 processClockResult(".gpu_time", mGPUTimeNs * 1e-9);
491 }
492
493 if (gVerboseLogging)
494 {
495 double fps = static_cast<double>(mTrialNumStepsPerformed * mIterationsPerStep) /
496 mTrialTimer.getElapsedWallClockTime();
497 printf("Ran %0.2lf iterations per second\n", fps);
498 }
499
500 mReporter->AddResult(".trial_steps", static_cast<size_t>(mTrialNumStepsPerformed));
501 mReporter->AddResult(".total_steps", static_cast<size_t>(mTotalNumStepsPerformed));
502
503 if (!mProcessMemoryUsageKBSamples.empty())
504 {
505 std::sort(mProcessMemoryUsageKBSamples.begin(), mProcessMemoryUsageKBSamples.end());
506
507 // Compute median.
508 size_t medianIndex = mProcessMemoryUsageKBSamples.size() / 2;
509 uint64_t medianMemoryKB = mProcessMemoryUsageKBSamples[medianIndex];
510 auto peakMemoryIterator = std::max_element(mProcessMemoryUsageKBSamples.begin(),
511 mProcessMemoryUsageKBSamples.end());
512 uint64_t peakMemoryKB = *peakMemoryIterator;
513
514 processMemoryResult(kMedianMemoryMetric, medianMemoryKB);
515 processMemoryResult(kPeakMemoryMetric, peakMemoryKB);
516 }
517
518 for (const auto &iter : mPerfCounterInfo)
519 {
520 const std::string &counterName = iter.second.name;
521 std::vector<GLuint64> samples = iter.second.samples;
522
523 // Median
524 {
525 size_t midpoint = samples.size() / 2;
526 std::nth_element(samples.begin(), samples.begin() + midpoint, samples.end());
527
528 std::string medianName = "." + counterName + "_median";
529 recordIntegerMetric(medianName.c_str(), static_cast<size_t>(samples[midpoint]),
530 "count");
531 addHistogramSample(medianName.c_str(), static_cast<double>(samples[midpoint]), "count");
532 }
533
534 // Maximum
535 {
536 const auto &maxIt = std::max_element(samples.begin(), samples.end());
537
538 std::string maxName = "." + counterName + "_max";
539 recordIntegerMetric(maxName.c_str(), static_cast<size_t>(*maxIt), "count");
540 addHistogramSample(maxName.c_str(), static_cast<double>(*maxIt), "count");
541 }
542
543 // Sum
544 {
545 GLuint64 sum =
546 std::accumulate(samples.begin(), samples.end(), static_cast<GLuint64>(0));
547
548 std::string sumName = "." + counterName + "_max";
549 recordIntegerMetric(sumName.c_str(), static_cast<size_t>(sum), "count");
550 addHistogramSample(sumName.c_str(), static_cast<double>(sum), "count");
551 }
552 }
553 }
554
processClockResult(const char * metric,double resultSeconds)555 void ANGLEPerfTest::processClockResult(const char *metric, double resultSeconds)
556 {
557 double secondsPerStep = resultSeconds / static_cast<double>(mTrialNumStepsPerformed);
558 double secondsPerIteration = secondsPerStep / static_cast<double>(mIterationsPerStep);
559
560 perf_test::MetricInfo metricInfo;
561 std::string units;
562 bool foundMetric = mReporter->GetMetricInfo(metric, &metricInfo);
563 if (!foundMetric)
564 {
565 fprintf(stderr, "Error getting metric info for %s.\n", metric);
566 return;
567 }
568 units = metricInfo.units;
569
570 double result;
571
572 if (units == "ms")
573 {
574 result = secondsPerIteration * kMilliSecondsPerSecond;
575 }
576 else if (units == "us")
577 {
578 result = secondsPerIteration * kMicroSecondsPerSecond;
579 }
580 else
581 {
582 result = secondsPerIteration * kNanoSecondsPerSecond;
583 }
584 recordDoubleMetric(metric, result, units);
585 addHistogramSample(metric, secondsPerIteration * kMilliSecondsPerSecond,
586 "msBestFitFormat_smallerIsBetter");
587 }
588
processMemoryResult(const char * metric,uint64_t resultKB)589 void ANGLEPerfTest::processMemoryResult(const char *metric, uint64_t resultKB)
590 {
591 perf_test::MetricInfo metricInfo;
592 if (!mReporter->GetMetricInfo(metric, &metricInfo))
593 {
594 mReporter->RegisterImportantMetric(metric, "sizeInBytes");
595 }
596
597 recordIntegerMetric(metric, static_cast<size_t>(resultKB * 1000), "sizeInBytes");
598 addHistogramSample(metric, static_cast<double>(resultKB) * 1000.0,
599 "sizeInBytes_smallerIsBetter");
600 }
601
normalizedTime(size_t value) const602 double ANGLEPerfTest::normalizedTime(size_t value) const
603 {
604 return static_cast<double>(value) / static_cast<double>(mTrialNumStepsPerformed);
605 }
606
getStepAlignment() const607 int ANGLEPerfTest::getStepAlignment() const
608 {
609 // Default: No special alignment rules.
610 return 1;
611 }
612
RenderTestParams()613 RenderTestParams::RenderTestParams()
614 {
615 #if defined(ANGLE_DEBUG_LAYERS_ENABLED)
616 eglParameters.debugLayersEnabled = true;
617 #else
618 eglParameters.debugLayersEnabled = false;
619 #endif
620 }
621
backend() const622 std::string RenderTestParams::backend() const
623 {
624 std::stringstream strstr;
625
626 switch (driver)
627 {
628 case GLESDriverType::AngleEGL:
629 break;
630 case GLESDriverType::AngleVulkanSecondariesEGL:
631 strstr << "_vulkan_secondaries";
632 break;
633 case GLESDriverType::SystemWGL:
634 case GLESDriverType::SystemEGL:
635 strstr << "_native";
636 break;
637 case GLESDriverType::ZinkEGL:
638 strstr << "_zink";
639 break;
640 default:
641 assert(0);
642 return "_unk";
643 }
644
645 switch (getRenderer())
646 {
647 case EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE:
648 break;
649 case EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE:
650 strstr << "_d3d11";
651 break;
652 case EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE:
653 strstr << "_d3d9";
654 break;
655 case EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE:
656 strstr << "_gl";
657 break;
658 case EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE:
659 strstr << "_gles";
660 break;
661 case EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE:
662 strstr << "_vulkan";
663 break;
664 case EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE:
665 strstr << "_metal";
666 break;
667 default:
668 assert(0);
669 return "_unk";
670 }
671
672 switch (eglParameters.deviceType)
673 {
674 case EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE:
675 strstr << "_null";
676 break;
677 case EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE:
678 strstr << "_swiftshader";
679 break;
680 default:
681 break;
682 }
683
684 return strstr.str();
685 }
686
story() const687 std::string RenderTestParams::story() const
688 {
689 std::stringstream strstr;
690
691 switch (surfaceType)
692 {
693 case SurfaceType::Window:
694 break;
695 case SurfaceType::WindowWithVSync:
696 strstr << "_vsync";
697 break;
698 case SurfaceType::Offscreen:
699 strstr << "_offscreen";
700 break;
701 default:
702 UNREACHABLE();
703 return "";
704 }
705
706 if (multisample)
707 {
708 strstr << "_" << samples << "_samples";
709 }
710
711 return strstr.str();
712 }
713
backendAndStory() const714 std::string RenderTestParams::backendAndStory() const
715 {
716 return backend() + story();
717 }
718
ANGLERenderTest(const std::string & name,const RenderTestParams & testParams,const char * units)719 ANGLERenderTest::ANGLERenderTest(const std::string &name,
720 const RenderTestParams &testParams,
721 const char *units)
722 : ANGLEPerfTest(name,
723 testParams.backend(),
724 testParams.story(),
725 OneFrame() ? 1 : testParams.iterationsPerStep,
726 units),
727 mTestParams(testParams),
728 mIsTimestampQueryAvailable(false),
729 mGLWindow(nullptr),
730 mOSWindow(nullptr),
731 mSwapEnabled(true)
732 {
733 // Force fast tests to make sure our slowest bots don't time out.
734 if (OneFrame())
735 {
736 const_cast<RenderTestParams &>(testParams).iterationsPerStep = 1;
737 }
738
739 // Try to ensure we don't trigger allocation during execution.
740 mTraceEventBuffer.reserve(kInitialTraceEventBufferSize);
741
742 switch (testParams.driver)
743 {
744 case GLESDriverType::AngleEGL:
745 mGLWindow = EGLWindow::New(testParams.clientType, testParams.majorVersion,
746 testParams.minorVersion, testParams.profileMask);
747 mEntryPointsLib.reset(OpenSharedLibrary(ANGLE_EGL_LIBRARY_NAME, SearchType::ModuleDir));
748 break;
749 case GLESDriverType::AngleVulkanSecondariesEGL:
750 mGLWindow = EGLWindow::New(testParams.clientType, testParams.majorVersion,
751 testParams.minorVersion, testParams.profileMask);
752 mEntryPointsLib.reset(OpenSharedLibrary(ANGLE_VULKAN_SECONDARIES_EGL_LIBRARY_NAME,
753 SearchType::ModuleDir));
754 break;
755 case GLESDriverType::SystemEGL:
756 #if defined(ANGLE_USE_UTIL_LOADER) && !defined(ANGLE_PLATFORM_WINDOWS)
757 mGLWindow = EGLWindow::New(testParams.clientType, testParams.majorVersion,
758 testParams.minorVersion, testParams.profileMask);
759 mEntryPointsLib.reset(OpenSharedLibraryWithExtension(
760 GetNativeEGLLibraryNameWithExtension(), SearchType::SystemDir));
761 #else
762 skipTest("Not implemented.");
763 #endif // defined(ANGLE_USE_UTIL_LOADER) && !defined(ANGLE_PLATFORM_WINDOWS)
764 break;
765 case GLESDriverType::SystemWGL:
766 #if defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
767 mGLWindow = WGLWindow::New(testParams.clientType, testParams.majorVersion,
768 testParams.minorVersion, testParams.profileMask);
769 mEntryPointsLib.reset(OpenSharedLibrary("opengl32", SearchType::SystemDir));
770 #else
771 skipTest("WGL driver not available.");
772 #endif // defined(ANGLE_USE_UTIL_LOADER) && defined(ANGLE_PLATFORM_WINDOWS)
773 break;
774 case GLESDriverType::ZinkEGL:
775 mGLWindow = EGLWindow::New(testParams.clientType, testParams.majorVersion,
776 testParams.minorVersion, testParams.profileMask);
777 mEntryPointsLib.reset(
778 OpenSharedLibrary(ANGLE_MESA_EGL_LIBRARY_NAME, SearchType::ModuleDir));
779 break;
780 default:
781 skipTest("Error in switch.");
782 break;
783 }
784 }
785
~ANGLERenderTest()786 ANGLERenderTest::~ANGLERenderTest()
787 {
788 OSWindow::Delete(&mOSWindow);
789 GLWindowBase::Delete(&mGLWindow);
790 }
791
addExtensionPrerequisite(std::string extensionName)792 void ANGLERenderTest::addExtensionPrerequisite(std::string extensionName)
793 {
794 mExtensionPrerequisites.push_back(extensionName);
795 }
796
addIntegerPrerequisite(GLenum target,int min)797 void ANGLERenderTest::addIntegerPrerequisite(GLenum target, int min)
798 {
799 mIntegerPrerequisites.push_back({target, min});
800 }
801
SetUp()802 void ANGLERenderTest::SetUp()
803 {
804 if (mSkipTest)
805 {
806 return;
807 }
808
809 // Set a consistent CPU core affinity and high priority.
810 StabilizeCPUForBenchmarking();
811
812 mOSWindow = OSWindow::New();
813
814 if (!mGLWindow)
815 {
816 skipTest("!mGLWindow");
817 return;
818 }
819
820 mPlatformMethods.logError = CustomLogError;
821 mPlatformMethods.logWarning = EmptyPlatformMethod;
822 mPlatformMethods.logInfo = EmptyPlatformMethod;
823 mPlatformMethods.addTraceEvent = AddPerfTraceEvent;
824 mPlatformMethods.getTraceCategoryEnabledFlag = GetPerfTraceCategoryEnabled;
825 mPlatformMethods.updateTraceEventDuration = UpdateTraceEventDuration;
826 mPlatformMethods.monotonicallyIncreasingTime = MonotonicallyIncreasingTime;
827 mPlatformMethods.context = this;
828
829 if (!mOSWindow->initialize(mName, mTestParams.windowWidth, mTestParams.windowHeight))
830 {
831 failTest("Failed initializing OSWindow");
832 return;
833 }
834
835 // Override platform method parameter.
836 EGLPlatformParameters withMethods = mTestParams.eglParameters;
837 withMethods.platformMethods = &mPlatformMethods;
838
839 // Request a common framebuffer config
840 mConfigParams.redBits = 8;
841 mConfigParams.greenBits = 8;
842 mConfigParams.blueBits = 8;
843 mConfigParams.alphaBits = 8;
844 mConfigParams.depthBits = 24;
845 mConfigParams.stencilBits = 8;
846 mConfigParams.colorSpace = mTestParams.colorSpace;
847 mConfigParams.multisample = mTestParams.multisample;
848 mConfigParams.samples = mTestParams.samples;
849 if (mTestParams.surfaceType != SurfaceType::WindowWithVSync)
850 {
851 mConfigParams.swapInterval = 0;
852 }
853
854 if (gPrintExtensionsToFile != nullptr || gRequestedExtensions != nullptr)
855 {
856 mConfigParams.extensionsEnabled = false;
857 }
858
859 GLWindowResult res = mGLWindow->initializeGLWithResult(
860 mOSWindow, mEntryPointsLib.get(), mTestParams.driver, withMethods, mConfigParams);
861 switch (res)
862 {
863 case GLWindowResult::NoColorspaceSupport:
864 skipTest("Missing support for color spaces.");
865 return;
866 case GLWindowResult::Error:
867 failTest("Failed initializing GL Window");
868 return;
869 default:
870 break;
871 }
872
873 if (gPrintExtensionsToFile)
874 {
875 std::ofstream fout(gPrintExtensionsToFile);
876 if (fout.is_open())
877 {
878 int numExtensions = 0;
879 glGetIntegerv(GL_NUM_REQUESTABLE_EXTENSIONS_ANGLE, &numExtensions);
880 for (int ext = 0; ext < numExtensions; ext++)
881 {
882 fout << glGetStringi(GL_REQUESTABLE_EXTENSIONS_ANGLE, ext) << std::endl;
883 }
884 fout.close();
885 std::stringstream statusString;
886 statusString << "Wrote out to file: " << gPrintExtensionsToFile;
887 skipTest(statusString.str());
888 }
889 else
890 {
891 std::stringstream failStr;
892 failStr << "Failed to open file: " << gPrintExtensionsToFile;
893 failTest(failStr.str());
894 }
895 return;
896 }
897
898 if (gRequestedExtensions != nullptr)
899 {
900 std::istringstream ss{gRequestedExtensions};
901 std::string ext;
902 while (std::getline(ss, ext, ' '))
903 {
904 glRequestExtensionANGLE(ext.c_str());
905 }
906 }
907
908 // Disable vsync (if not done by the window init).
909 if (mTestParams.surfaceType != SurfaceType::WindowWithVSync)
910 {
911 if (!mGLWindow->setSwapInterval(0))
912 {
913 failTest("Failed setting swap interval");
914 return;
915 }
916 }
917
918 if (mTestParams.trackGpuTime)
919 {
920 mIsTimestampQueryAvailable = EnsureGLExtensionEnabled("GL_EXT_disjoint_timer_query");
921 }
922
923 skipTestIfMissingExtensionPrerequisites();
924 skipTestIfFailsIntegerPrerequisite();
925
926 if (mSkipTest)
927 {
928 GTEST_SKIP() << mSkipTestReason;
929 // GTEST_SKIP returns.
930 }
931
932 #if defined(ANGLE_ENABLE_ASSERTS)
933 if (IsGLExtensionEnabled("GL_KHR_debug") && mEnableDebugCallback)
934 {
935 EnableDebugCallback(&PerfTestDebugCallback, this);
936 }
937 #endif
938
939 initializeBenchmark();
940
941 if (mSkipTest)
942 {
943 GTEST_SKIP() << mSkipTestReason;
944 // GTEST_SKIP returns.
945 }
946
947 if (mTestParams.iterationsPerStep == 0)
948 {
949 failTest("Please initialize 'iterationsPerStep'.");
950 return;
951 }
952
953 if (gVerboseLogging)
954 {
955 printf("GL_RENDERER: %s\n", glGetString(GL_RENDERER));
956 printf("GL_VERSION: %s\n", glGetString(GL_VERSION));
957 }
958
959 mTestTrialResults.reserve(gTestTrials);
960
961 // Runs warmup if enabled
962 ANGLEPerfTest::SetUp();
963
964 initPerfCounters();
965 }
966
TearDown()967 void ANGLERenderTest::TearDown()
968 {
969 ASSERT(mTimestampQueries.empty());
970
971 if (!mSkipTest)
972 {
973 destroyBenchmark();
974 }
975
976 if (mGLWindow)
977 {
978 mGLWindow->destroyGL();
979 mGLWindow = nullptr;
980 }
981
982 if (mOSWindow)
983 {
984 mOSWindow->destroy();
985 mOSWindow = nullptr;
986 }
987
988 // Dump trace events to json file.
989 if (gEnableTrace)
990 {
991 DumpTraceEventsToJSONFile(mTraceEventBuffer, gTraceFile);
992 }
993
994 ANGLEPerfTest::TearDown();
995 }
996
initPerfCounters()997 void ANGLERenderTest::initPerfCounters()
998 {
999 if (!gPerfCounters)
1000 {
1001 return;
1002 }
1003
1004 if (!IsGLExtensionEnabled(kPerfMonitorExtensionName))
1005 {
1006 fprintf(stderr, "Cannot report perf metrics because %s is not available.\n",
1007 kPerfMonitorExtensionName);
1008 return;
1009 }
1010
1011 CounterNameToIndexMap indexMap = BuildCounterNameToIndexMap();
1012
1013 std::vector<std::string> counters =
1014 angle::SplitString(gPerfCounters, ":", angle::WhitespaceHandling::TRIM_WHITESPACE,
1015 angle::SplitResult::SPLIT_WANT_NONEMPTY);
1016 for (const std::string &counter : counters)
1017 {
1018 bool found = false;
1019
1020 for (const auto &indexMapIter : indexMap)
1021 {
1022 const std::string &indexMapName = indexMapIter.first;
1023 if (NamesMatchWithWildcard(counter.c_str(), indexMapName.c_str()))
1024 {
1025 {
1026 std::stringstream medianStr;
1027 medianStr << '.' << indexMapName << "_median";
1028 std::string medianName = medianStr.str();
1029 mReporter->RegisterImportantMetric(medianName, "count");
1030 }
1031
1032 {
1033 std::stringstream maxStr;
1034 maxStr << '.' << indexMapName << "_max";
1035 std::string maxName = maxStr.str();
1036 mReporter->RegisterImportantMetric(maxName, "count");
1037 }
1038
1039 {
1040 std::stringstream sumStr;
1041 sumStr << '.' << indexMapName << "_sum";
1042 std::string sumName = sumStr.str();
1043 mReporter->RegisterImportantMetric(sumName, "count");
1044 }
1045
1046 GLuint index = indexMapIter.second;
1047 mPerfCounterInfo[index] = {indexMapName, {}};
1048
1049 found = true;
1050 }
1051 }
1052
1053 if (!found)
1054 {
1055 fprintf(stderr, "'%s' does not match any available perf counters.\n", counter.c_str());
1056 }
1057 }
1058 }
1059
updatePerfCounters()1060 void ANGLERenderTest::updatePerfCounters()
1061 {
1062 if (mPerfCounterInfo.empty())
1063 {
1064 return;
1065 }
1066
1067 std::vector<PerfMonitorTriplet> perfData = GetPerfMonitorTriplets();
1068 ASSERT(!perfData.empty());
1069
1070 for (auto &iter : mPerfCounterInfo)
1071 {
1072 uint32_t counter = iter.first;
1073 std::vector<GLuint64> &samples = iter.second.samples;
1074 samples.push_back(perfData[counter].value);
1075 }
1076 }
1077
beginInternalTraceEvent(const char * name)1078 void ANGLERenderTest::beginInternalTraceEvent(const char *name)
1079 {
1080 if (gEnableTrace)
1081 {
1082 mTraceEventBuffer.emplace_back(TRACE_EVENT_PHASE_BEGIN, gTraceCategories[0].name, name,
1083 MonotonicallyIncreasingTime(&mPlatformMethods),
1084 getCurrentThreadSerial());
1085 }
1086 }
1087
endInternalTraceEvent(const char * name)1088 void ANGLERenderTest::endInternalTraceEvent(const char *name)
1089 {
1090 if (gEnableTrace)
1091 {
1092 mTraceEventBuffer.emplace_back(TRACE_EVENT_PHASE_END, gTraceCategories[0].name, name,
1093 MonotonicallyIncreasingTime(&mPlatformMethods),
1094 getCurrentThreadSerial());
1095 }
1096 }
1097
beginGLTraceEvent(const char * name,double hostTimeSec)1098 void ANGLERenderTest::beginGLTraceEvent(const char *name, double hostTimeSec)
1099 {
1100 if (gEnableTrace)
1101 {
1102 mTraceEventBuffer.emplace_back(TRACE_EVENT_PHASE_BEGIN, gTraceCategories[1].name, name,
1103 hostTimeSec, getCurrentThreadSerial());
1104 }
1105 }
1106
endGLTraceEvent(const char * name,double hostTimeSec)1107 void ANGLERenderTest::endGLTraceEvent(const char *name, double hostTimeSec)
1108 {
1109 if (gEnableTrace)
1110 {
1111 mTraceEventBuffer.emplace_back(TRACE_EVENT_PHASE_END, gTraceCategories[1].name, name,
1112 hostTimeSec, getCurrentThreadSerial());
1113 }
1114 }
1115
step()1116 void ANGLERenderTest::step()
1117 {
1118 beginInternalTraceEvent("step");
1119
1120 // Clear events that the application did not process from this frame
1121 Event event;
1122 bool closed = false;
1123 while (popEvent(&event))
1124 {
1125 // If the application did not catch a close event, close now
1126 if (event.Type == Event::EVENT_CLOSED)
1127 {
1128 closed = true;
1129 }
1130 }
1131
1132 if (closed)
1133 {
1134 abortTest();
1135 }
1136 else
1137 {
1138 drawBenchmark();
1139
1140 // Swap is needed so that the GPU driver will occasionally flush its
1141 // internal command queue to the GPU. This is enabled for null back-end
1142 // devices because some back-ends (e.g. Vulkan) also accumulate internal
1143 // command queues.
1144 if (mSwapEnabled)
1145 {
1146 updatePerfCounters();
1147 mGLWindow->swap();
1148 }
1149 mOSWindow->messageLoop();
1150
1151 #if defined(ANGLE_ENABLE_ASSERTS)
1152 if (!gRetraceMode)
1153 {
1154 EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
1155 }
1156 #endif // defined(ANGLE_ENABLE_ASSERTS)
1157
1158 // Sample system memory
1159 uint64_t processMemoryUsageKB = GetProcessMemoryUsageKB();
1160 if (processMemoryUsageKB)
1161 {
1162 mProcessMemoryUsageKBSamples.push_back(processMemoryUsageKB);
1163 }
1164 }
1165
1166 endInternalTraceEvent("step");
1167 }
1168
startGpuTimer()1169 void ANGLERenderTest::startGpuTimer()
1170 {
1171 if (mTestParams.trackGpuTime && mIsTimestampQueryAvailable)
1172 {
1173 glGenQueriesEXT(1, &mCurrentTimestampBeginQuery);
1174 glQueryCounterEXT(mCurrentTimestampBeginQuery, GL_TIMESTAMP_EXT);
1175 }
1176 }
1177
stopGpuTimer()1178 void ANGLERenderTest::stopGpuTimer()
1179 {
1180 if (mTestParams.trackGpuTime && mIsTimestampQueryAvailable)
1181 {
1182 GLuint endQuery = 0;
1183 glGenQueriesEXT(1, &endQuery);
1184 glQueryCounterEXT(endQuery, GL_TIMESTAMP_EXT);
1185 mTimestampQueries.push({mCurrentTimestampBeginQuery, endQuery});
1186 }
1187 }
1188
computeGPUTime()1189 void ANGLERenderTest::computeGPUTime()
1190 {
1191 if (mTestParams.trackGpuTime && mIsTimestampQueryAvailable)
1192 {
1193 while (!mTimestampQueries.empty())
1194 {
1195 const TimestampSample &sample = mTimestampQueries.front();
1196 GLuint available = GL_FALSE;
1197 glGetQueryObjectuivEXT(sample.endQuery, GL_QUERY_RESULT_AVAILABLE_EXT, &available);
1198 if (available != GL_TRUE)
1199 {
1200 // query is not completed yet, bail out
1201 break;
1202 }
1203
1204 // frame's begin query must also completed.
1205 glGetQueryObjectuivEXT(sample.beginQuery, GL_QUERY_RESULT_AVAILABLE_EXT, &available);
1206 ASSERT(available == GL_TRUE);
1207
1208 // Retrieve query result
1209 uint64_t beginGLTimeNs = 0;
1210 uint64_t endGLTimeNs = 0;
1211 glGetQueryObjectui64vEXT(sample.beginQuery, GL_QUERY_RESULT_EXT, &beginGLTimeNs);
1212 glGetQueryObjectui64vEXT(sample.endQuery, GL_QUERY_RESULT_EXT, &endGLTimeNs);
1213 glDeleteQueriesEXT(1, &sample.beginQuery);
1214 glDeleteQueriesEXT(1, &sample.endQuery);
1215 mTimestampQueries.pop();
1216
1217 // compute GPU time
1218 mGPUTimeNs += endGLTimeNs - beginGLTimeNs;
1219 }
1220 }
1221 }
1222
startTest()1223 void ANGLERenderTest::startTest() {}
1224
finishTest()1225 void ANGLERenderTest::finishTest()
1226 {
1227 if (mTestParams.eglParameters.deviceType != EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE &&
1228 !gNoFinish && !gRetraceMode)
1229 {
1230 FinishAndCheckForContextLoss();
1231 }
1232 }
1233
popEvent(Event * event)1234 bool ANGLERenderTest::popEvent(Event *event)
1235 {
1236 return mOSWindow->popEvent(event);
1237 }
1238
getWindow()1239 OSWindow *ANGLERenderTest::getWindow()
1240 {
1241 return mOSWindow;
1242 }
1243
getGLWindow()1244 GLWindowBase *ANGLERenderTest::getGLWindow()
1245 {
1246 return mGLWindow;
1247 }
1248
skipTestIfMissingExtensionPrerequisites()1249 void ANGLERenderTest::skipTestIfMissingExtensionPrerequisites()
1250 {
1251 for (std::string extension : mExtensionPrerequisites)
1252 {
1253 if (!CheckExtensionExists(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)),
1254 extension))
1255 {
1256 skipTest(std::string("Test skipped due to missing extension: ") + extension);
1257 return;
1258 }
1259 }
1260 }
1261
skipTestIfFailsIntegerPrerequisite()1262 void ANGLERenderTest::skipTestIfFailsIntegerPrerequisite()
1263 {
1264 for (const auto [target, minRequired] : mIntegerPrerequisites)
1265 {
1266 GLint driverValue;
1267 glGetIntegerv(target, &driverValue);
1268 if (static_cast<int>(driverValue) < minRequired)
1269 {
1270 std::stringstream ss;
1271 ss << "Test skipped due to value (" << std::to_string(static_cast<int>(driverValue))
1272 << ") being less than the prerequisite minimum (" << std::to_string(minRequired)
1273 << ") for GL constant " << gl::GLenumToString(gl::GLESEnum::AllEnums, target);
1274 skipTest(ss.str());
1275 }
1276 }
1277 }
1278
setWebGLCompatibilityEnabled(bool webglCompatibility)1279 void ANGLERenderTest::setWebGLCompatibilityEnabled(bool webglCompatibility)
1280 {
1281 mConfigParams.webGLCompatibility = webglCompatibility;
1282 }
1283
setRobustResourceInit(bool enabled)1284 void ANGLERenderTest::setRobustResourceInit(bool enabled)
1285 {
1286 mConfigParams.robustResourceInit = enabled;
1287 }
1288
getTraceEventBuffer()1289 std::vector<TraceEvent> &ANGLERenderTest::getTraceEventBuffer()
1290 {
1291 return mTraceEventBuffer;
1292 }
1293
onErrorMessage(const char * errorMessage)1294 void ANGLERenderTest::onErrorMessage(const char *errorMessage)
1295 {
1296 abortTest();
1297 std::ostringstream err;
1298 err << "Failing test because of unexpected error:\n" << errorMessage << "\n";
1299 failTest(err.str());
1300 }
1301
getCurrentThreadSerial()1302 uint32_t ANGLERenderTest::getCurrentThreadSerial()
1303 {
1304 uint64_t id = angle::GetCurrentThreadUniqueId();
1305
1306 for (uint32_t serial = 0; serial < static_cast<uint32_t>(mThreadIDs.size()); ++serial)
1307 {
1308 if (mThreadIDs[serial] == id)
1309 {
1310 return serial + 1;
1311 }
1312 }
1313
1314 mThreadIDs.push_back(id);
1315 return static_cast<uint32_t>(mThreadIDs.size());
1316 }
1317
1318 namespace angle
1319 {
GetHostTimeSeconds()1320 double GetHostTimeSeconds()
1321 {
1322 // Move the time origin to the first call to this function, to avoid generating unnecessarily
1323 // large timestamps.
1324 static double origin = GetCurrentSystemTime();
1325 return GetCurrentSystemTime() - origin;
1326 }
1327 } // namespace angle
1328