• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &params)
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 &params = 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