1 //
2 // Copyright 2020 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 // TracePerf:
7 // Performance test for ANGLE replaying traces.
8 //
9
10 #include <gtest/gtest.h>
11 #include "common/PackedEnums.h"
12 #include "common/system_utils.h"
13 #include "tests/perf_tests/ANGLEPerfTest.h"
14 #include "tests/perf_tests/DrawCallPerfParams.h"
15 #include "util/egl_loader_autogen.h"
16 #include "util/frame_capture_utils.h"
17 #include "util/png_utils.h"
18
19 #include "restricted_traces/restricted_traces_autogen.h"
20
21 #include <cassert>
22 #include <functional>
23 #include <sstream>
24
25 using namespace angle;
26 using namespace egl_platform;
27
28 namespace
29 {
30 void FramebufferChangeCallback(void *userData, GLenum target, GLuint framebuffer);
31
32 struct TracePerfParams final : public RenderTestParams
33 {
34 // Common default options
TracePerfParams__anone856e07b0111::TracePerfParams35 TracePerfParams()
36 {
37 majorVersion = 3;
38 minorVersion = 0;
39 windowWidth = 1920;
40 windowHeight = 1080;
41 trackGpuTime = true;
42
43 // Display the frame after every drawBenchmark invocation
44 iterationsPerStep = 1;
45 }
46
story__anone856e07b0111::TracePerfParams47 std::string story() const override
48 {
49 std::stringstream strstr;
50 strstr << RenderTestParams::story() << "_" << kTraceInfos[testID].name;
51 return strstr.str();
52 }
53
54 RestrictedTraceID testID;
55 };
56
operator <<(std::ostream & os,const TracePerfParams & params)57 std::ostream &operator<<(std::ostream &os, const TracePerfParams ¶ms)
58 {
59 os << params.backendAndStory().substr(1);
60 return os;
61 }
62
63 class TracePerfTest : public ANGLERenderTest, public ::testing::WithParamInterface<TracePerfParams>
64 {
65 public:
66 TracePerfTest();
67
68 void initializeBenchmark() override;
69 void destroyBenchmark() override;
70 void drawBenchmark() override;
71
72 void onFramebufferChange(GLenum target, GLuint framebuffer);
73
74 uint32_t mStartFrame;
75 uint32_t mEndFrame;
76
77 double getHostTimeFromGLTime(GLint64 glTime);
78
79 private:
80 struct QueryInfo
81 {
82 GLuint beginTimestampQuery;
83 GLuint endTimestampQuery;
84 GLuint framebuffer;
85 };
86
87 struct TimeSample
88 {
89 GLint64 glTime;
90 double hostTime;
91 };
92
93 void sampleTime();
94 void saveScreenshot(const std::string &screenshotName) override;
95
96 // For tracking RenderPass/FBO change timing.
97 QueryInfo mCurrentQuery = {};
98 std::vector<QueryInfo> mRunningQueries;
99 std::vector<TimeSample> mTimeline;
100
101 std::string mStartingDirectory;
102 };
103
TracePerfTest()104 TracePerfTest::TracePerfTest()
105 : ANGLERenderTest("TracePerf", GetParam()), mStartFrame(0), mEndFrame(0)
106 {
107 // TODO(anglebug.com/4533) This fails after the upgrade to the 26.20.100.7870 driver.
108 if (IsWindows() && IsIntel() &&
109 GetParam().getRenderer() == EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE &&
110 GetParam().testID == RestrictedTraceID::manhattan_10)
111 {
112 mSkipTest = true;
113 }
114
115 // We already swap in TracePerfTest::drawBenchmark, no need to swap again in the harness.
116 disableTestHarnessSwap();
117 }
118
initializeBenchmark()119 void TracePerfTest::initializeBenchmark()
120 {
121 const auto ¶ms = GetParam();
122
123 mStartingDirectory = angle::GetCWD().value();
124
125 // To load the trace data path correctly we set the CWD to the executable dir.
126 if (!IsAndroid())
127 {
128 std::string exeDir = angle::GetExecutableDirectory();
129 angle::SetCWD(exeDir.c_str());
130 }
131
132 const TraceInfo &traceInfo = kTraceInfos[params.testID];
133 mStartFrame = traceInfo.startFrame;
134 mEndFrame = traceInfo.endFrame;
135 SetBinaryDataDecompressCallback(params.testID, DecompressBinaryData);
136
137 std::stringstream testDataDirStr;
138 testDataDirStr << ANGLE_TRACE_DATA_DIR << "/" << traceInfo.name;
139 std::string testDataDir = testDataDirStr.str();
140 SetBinaryDataDir(params.testID, testDataDir.c_str());
141
142 // Potentially slow. Can load a lot of resources.
143 SetupReplay(params.testID);
144 glFinish();
145
146 ASSERT_TRUE(mEndFrame > mStartFrame);
147
148 getWindow()->setVisible(true);
149 }
150
151 #undef TRACE_TEST_CASE
152
destroyBenchmark()153 void TracePerfTest::destroyBenchmark()
154 {
155 // In order for the next test to load, restore the working directory
156 angle::SetCWD(mStartingDirectory.c_str());
157 }
158
sampleTime()159 void TracePerfTest::sampleTime()
160 {
161 if (mIsTimestampQueryAvailable)
162 {
163 GLint64 glTime;
164 // glGetInteger64vEXT is exported by newer versions of the timer query extensions.
165 // Unfortunately only the core EP is exposed by some desktop drivers (e.g. NVIDIA).
166 if (glGetInteger64vEXT)
167 {
168 glGetInteger64vEXT(GL_TIMESTAMP_EXT, &glTime);
169 }
170 else
171 {
172 glGetInteger64v(GL_TIMESTAMP_EXT, &glTime);
173 }
174 mTimeline.push_back({glTime, angle::GetHostTimeSeconds()});
175 }
176 }
177
drawBenchmark()178 void TracePerfTest::drawBenchmark()
179 {
180 // Add a time sample from GL and the host.
181 sampleTime();
182
183 startGpuTimer();
184
185 for (uint32_t frame = mStartFrame; frame <= mEndFrame; ++frame)
186 {
187 char frameName[32];
188 sprintf(frameName, "Frame %u", frame);
189 beginInternalTraceEvent(frameName);
190
191 ReplayFrame(GetParam().testID, frame);
192 getGLWindow()->swap();
193
194 endInternalTraceEvent(frameName);
195 }
196
197 ResetReplay(GetParam().testID);
198
199 // Process any running queries once per iteration.
200 for (size_t queryIndex = 0; queryIndex < mRunningQueries.size();)
201 {
202 const QueryInfo &query = mRunningQueries[queryIndex];
203
204 GLuint endResultAvailable = 0;
205 glGetQueryObjectuivEXT(query.endTimestampQuery, GL_QUERY_RESULT_AVAILABLE,
206 &endResultAvailable);
207
208 if (endResultAvailable == GL_TRUE)
209 {
210 char fboName[32];
211 sprintf(fboName, "FBO %u", query.framebuffer);
212
213 GLint64 beginTimestamp = 0;
214 glGetQueryObjecti64vEXT(query.beginTimestampQuery, GL_QUERY_RESULT, &beginTimestamp);
215 glDeleteQueriesEXT(1, &query.beginTimestampQuery);
216 double beginHostTime = getHostTimeFromGLTime(beginTimestamp);
217 beginGLTraceEvent(fboName, beginHostTime);
218
219 GLint64 endTimestamp = 0;
220 glGetQueryObjecti64vEXT(query.endTimestampQuery, GL_QUERY_RESULT, &endTimestamp);
221 glDeleteQueriesEXT(1, &query.endTimestampQuery);
222 double endHostTime = getHostTimeFromGLTime(endTimestamp);
223 endGLTraceEvent(fboName, endHostTime);
224
225 mRunningQueries.erase(mRunningQueries.begin() + queryIndex);
226 }
227 else
228 {
229 queryIndex++;
230 }
231 }
232
233 stopGpuTimer();
234 }
235
236 // Converts a GL timestamp into a host-side CPU time aligned with "GetHostTimeSeconds".
237 // This check is necessary to line up sampled trace events in a consistent timeline.
238 // Uses a linear interpolation from a series of samples. We do a blocking call to sample
239 // both host and GL time once per swap. We then find the two closest GL timestamps and
240 // interpolate the host times between them to compute our result. If we are past the last
241 // GL timestamp we sample a new data point pair.
getHostTimeFromGLTime(GLint64 glTime)242 double TracePerfTest::getHostTimeFromGLTime(GLint64 glTime)
243 {
244 // Find two samples to do a lerp.
245 size_t firstSampleIndex = mTimeline.size() - 1;
246 while (firstSampleIndex > 0)
247 {
248 if (mTimeline[firstSampleIndex].glTime < glTime)
249 {
250 break;
251 }
252 firstSampleIndex--;
253 }
254
255 // Add an extra sample if we're missing an ending sample.
256 if (firstSampleIndex == mTimeline.size() - 1)
257 {
258 sampleTime();
259 }
260
261 const TimeSample &start = mTimeline[firstSampleIndex];
262 const TimeSample &end = mTimeline[firstSampleIndex + 1];
263
264 // Note: we have observed in some odd cases later timestamps producing values that are
265 // smaller than preceding timestamps. This bears further investigation.
266
267 // Compute the scaling factor for the lerp.
268 double glDelta = static_cast<double>(glTime - start.glTime);
269 double glRange = static_cast<double>(end.glTime - start.glTime);
270 double t = glDelta / glRange;
271
272 // Lerp(t1, t2, t)
273 double hostRange = end.hostTime - start.hostTime;
274 return mTimeline[firstSampleIndex].hostTime + hostRange * t;
275 }
276
277 // Callback from the perf tests.
onFramebufferChange(GLenum target,GLuint framebuffer)278 void TracePerfTest::onFramebufferChange(GLenum target, GLuint framebuffer)
279 {
280 if (!mIsTimestampQueryAvailable)
281 return;
282
283 if (target != GL_FRAMEBUFFER && target != GL_DRAW_FRAMEBUFFER)
284 return;
285
286 // We have at most one active timestamp query at a time. This code will end the current
287 // query and immediately start a new one.
288 if (mCurrentQuery.beginTimestampQuery != 0)
289 {
290 glGenQueriesEXT(1, &mCurrentQuery.endTimestampQuery);
291 glQueryCounterEXT(mCurrentQuery.endTimestampQuery, GL_TIMESTAMP_EXT);
292 mRunningQueries.push_back(mCurrentQuery);
293 mCurrentQuery = {};
294 }
295
296 ASSERT(mCurrentQuery.beginTimestampQuery == 0);
297
298 glGenQueriesEXT(1, &mCurrentQuery.beginTimestampQuery);
299 glQueryCounterEXT(mCurrentQuery.beginTimestampQuery, GL_TIMESTAMP_EXT);
300 mCurrentQuery.framebuffer = framebuffer;
301 }
302
saveScreenshot(const std::string & screenshotName)303 void TracePerfTest::saveScreenshot(const std::string &screenshotName)
304 {
305 // Render a single frame.
306 RestrictedTraceID testID = GetParam().testID;
307 const TraceInfo &traceInfo = kTraceInfos[testID];
308 ReplayFrame(testID, traceInfo.startFrame);
309
310 // RGBA 4-byte data.
311 uint32_t pixelCount = mTestParams.windowWidth * mTestParams.windowHeight;
312 std::vector<uint8_t> pixelData(pixelCount * 4);
313
314 glBindFramebuffer(GL_FRAMEBUFFER, 0);
315 glReadPixels(0, 0, mTestParams.windowWidth, mTestParams.windowHeight, GL_RGBA, GL_UNSIGNED_BYTE,
316 pixelData.data());
317
318 // Convert to RGB and flip y.
319 std::vector<uint8_t> rgbData(pixelCount * 3);
320 for (EGLint y = 0; y < mTestParams.windowHeight; ++y)
321 {
322 for (EGLint x = 0; x < mTestParams.windowWidth; ++x)
323 {
324 EGLint srcPixel = x + y * mTestParams.windowWidth;
325 EGLint dstPixel = x + (mTestParams.windowHeight - y - 1) * mTestParams.windowWidth;
326 memcpy(&rgbData[dstPixel * 3], &pixelData[srcPixel * 4], 3);
327 }
328 }
329
330 angle::SavePNGRGB(screenshotName.c_str(), "ANGLE Screenshot", mTestParams.windowWidth,
331 mTestParams.windowHeight, rgbData);
332
333 // Finish the frame loop.
334 for (uint32_t nextFrame = traceInfo.startFrame + 1; nextFrame < traceInfo.endFrame; ++nextFrame)
335 {
336 ReplayFrame(testID, nextFrame);
337 }
338 getGLWindow()->swap();
339 glFinish();
340 }
341
FramebufferChangeCallback(void * userData,GLenum target,GLuint framebuffer)342 ANGLE_MAYBE_UNUSED void FramebufferChangeCallback(void *userData, GLenum target, GLuint framebuffer)
343 {
344 reinterpret_cast<TracePerfTest *>(userData)->onFramebufferChange(target, framebuffer);
345 }
346
TEST_P(TracePerfTest,Run)347 TEST_P(TracePerfTest, Run)
348 {
349 run();
350 }
351
CombineTestID(const TracePerfParams & in,RestrictedTraceID id)352 TracePerfParams CombineTestID(const TracePerfParams &in, RestrictedTraceID id)
353 {
354 TracePerfParams out = in;
355 out.testID = id;
356 return out;
357 }
358
359 using namespace params;
360 using P = TracePerfParams;
361
362 std::vector<P> gTestsWithID =
363 CombineWithValues({P()}, AllEnums<RestrictedTraceID>(), CombineTestID);
364 std::vector<P> gTestsWithRenderer = CombineWithFuncs(gTestsWithID, {Vulkan<P>, EGL<P>});
365 ANGLE_INSTANTIATE_TEST_ARRAY(TracePerfTest, gTestsWithRenderer);
366
367 } // anonymous namespace
368