1 /*-------------------------------------------------------------------------
2 * drawElements Quality Program OpenGL ES 2.0 Module
3 * -------------------------------------------------
4 *
5 * Copyright 2014 The Android Open Source Project
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief Flush and finish tests.
22 *//*--------------------------------------------------------------------*/
23
24 #include "es2fFlushFinishTests.hpp"
25
26 #include "gluRenderContext.hpp"
27 #include "gluObjectWrapper.hpp"
28 #include "gluShaderProgram.hpp"
29 #include "gluDrawUtil.hpp"
30
31 #include "glsCalibration.hpp"
32
33 #include "tcuTestLog.hpp"
34 #include "tcuRenderTarget.hpp"
35 #include "tcuCPUWarmup.hpp"
36
37 #include "glwEnums.hpp"
38 #include "glwFunctions.hpp"
39
40 #include "deRandom.hpp"
41 #include "deStringUtil.hpp"
42 #include "deClock.h"
43 #include "deThread.h"
44 #include "deMath.h"
45
46 #include <algorithm>
47
48 namespace deqp
49 {
50 namespace gles2
51 {
52 namespace Functional
53 {
54
55 using deqp::gls::LineParameters;
56 using deqp::gls::theilSenLinearRegression;
57 using std::string;
58 using std::vector;
59 using tcu::TestLog;
60 using tcu::Vec2;
61
62 namespace
63 {
64
65 enum
66 {
67 MAX_VIEWPORT_SIZE = 128,
68 MAX_SAMPLE_DURATION_US = 1000 * 1000,
69 WAIT_TIME_MS = 1200,
70 NUM_SAMPLES = 25,
71 MIN_DRAW_CALL_COUNT = 10,
72 MAX_DRAW_CALL_COUNT = 1 << 20,
73 NUM_ITERS_IN_SHADER = 10
74 };
75
76 const float NO_CORR_COEF_THRESHOLD = 0.1f;
77 const float FLUSH_COEF_THRESHOLD = 0.2f;
78 const float CORRELATED_COEF_THRESHOLD = 0.5f;
79
busyWait(int milliseconds)80 static void busyWait(int milliseconds)
81 {
82 const uint64_t startTime = deGetMicroseconds();
83 float v = 2.0f;
84
85 for (;;)
86 {
87 for (int i = 0; i < 10; i++)
88 v = deFloatSin(v);
89
90 if (deGetMicroseconds() - startTime >= uint64_t(1000 * milliseconds))
91 break;
92 }
93 }
94
95 class CalibrationFailedException : public std::runtime_error
96 {
97 public:
CalibrationFailedException(const std::string & reason)98 CalibrationFailedException(const std::string &reason) : std::runtime_error(reason)
99 {
100 }
101 };
102
103 class FlushFinishCase : public TestCase
104 {
105 public:
106 enum ExpectedBehavior
107 {
108 EXPECT_COEF_LESS_THAN = 0,
109 EXPECT_COEF_GREATER_THAN,
110 };
111
112 FlushFinishCase(Context &context, const char *name, const char *description, ExpectedBehavior waitBehavior,
113 float waitThreshold, ExpectedBehavior readBehavior, float readThreshold);
114 ~FlushFinishCase(void);
115
116 void init(void);
117 void deinit(void);
118 IterateResult iterate(void);
119
120 struct Sample
121 {
122 int numDrawCalls;
123 uint64_t waitTime;
124 uint64_t readPixelsTime;
125 };
126
127 struct CalibrationParams
128 {
129 int maxDrawCalls;
130 };
131
132 protected:
133 virtual void waitForGL(void) = 0;
134
135 private:
136 FlushFinishCase(const FlushFinishCase &);
137 FlushFinishCase &operator=(const FlushFinishCase &);
138
139 CalibrationParams calibrate(void);
140 void analyzeResults(const std::vector<Sample> &samples, const CalibrationParams &calibrationParams);
141
142 void setupRenderState(void);
143 void render(int numDrawCalls);
144 void readPixels(void);
145
146 const ExpectedBehavior m_waitBehavior;
147 const float m_waitThreshold;
148 const ExpectedBehavior m_readBehavior;
149 const float m_readThreshold;
150
151 glu::ShaderProgram *m_program;
152 };
153
FlushFinishCase(Context & context,const char * name,const char * description,ExpectedBehavior waitBehavior,float waitThreshold,ExpectedBehavior readBehavior,float readThreshold)154 FlushFinishCase::FlushFinishCase(Context &context, const char *name, const char *description,
155 ExpectedBehavior waitBehavior, float waitThreshold, ExpectedBehavior readBehavior,
156 float readThreshold)
157 : TestCase(context, name, description)
158 , m_waitBehavior(waitBehavior)
159 , m_waitThreshold(waitThreshold)
160 , m_readBehavior(readBehavior)
161 , m_readThreshold(readThreshold)
162 , m_program(DE_NULL)
163 {
164 }
165
~FlushFinishCase(void)166 FlushFinishCase::~FlushFinishCase(void)
167 {
168 FlushFinishCase::deinit();
169 }
170
init(void)171 void FlushFinishCase::init(void)
172 {
173 DE_ASSERT(!m_program);
174
175 m_program =
176 new glu::ShaderProgram(m_context.getRenderContext(),
177 glu::ProgramSources() << glu::VertexSource("attribute highp vec4 a_position;\n"
178 "varying highp vec4 v_coord;\n"
179 "void main (void)\n"
180 "{\n"
181 " gl_Position = a_position;\n"
182 " v_coord = a_position;\n"
183 "}\n")
184 << glu::FragmentSource("uniform mediump int u_numIters;\n"
185 "varying mediump vec4 v_coord;\n"
186 "void main (void)\n"
187 "{\n"
188 " highp vec4 color = v_coord;\n"
189 " for (int i = 0; i < " +
190 de::toString(int(NUM_ITERS_IN_SHADER)) +
191 "; i++)\n"
192 " color = sin(color);\n"
193 " gl_FragColor = color;\n"
194 "}\n"));
195
196 if (!m_program->isOk())
197 {
198 m_testCtx.getLog() << *m_program;
199 delete m_program;
200 m_program = DE_NULL;
201 TCU_FAIL("Compile failed");
202 }
203 }
204
deinit(void)205 void FlushFinishCase::deinit(void)
206 {
207 delete m_program;
208 m_program = DE_NULL;
209 }
210
operator <<(tcu::TestLog & log,const FlushFinishCase::Sample & sample)211 tcu::TestLog &operator<<(tcu::TestLog &log, const FlushFinishCase::Sample &sample)
212 {
213 log << TestLog::Message << sample.numDrawCalls << " calls:\t" << sample.waitTime << " us wait,\t"
214 << sample.readPixelsTime << " us read" << TestLog::EndMessage;
215 return log;
216 }
217
setupRenderState(void)218 void FlushFinishCase::setupRenderState(void)
219 {
220 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
221 const int posLoc = gl.getAttribLocation(m_program->getProgram(), "a_position");
222 const int viewportW = de::min<int>(m_context.getRenderTarget().getWidth(), MAX_VIEWPORT_SIZE);
223 const int viewportH = de::min<int>(m_context.getRenderTarget().getHeight(), MAX_VIEWPORT_SIZE);
224
225 static const float s_positions[] = {-1.0f, -1.0f, +1.0f, -1.0f, -1.0f, +1.0f, +1.0f, +1.0f};
226
227 TCU_CHECK(posLoc >= 0);
228
229 gl.viewport(0, 0, viewportW, viewportH);
230 gl.useProgram(m_program->getProgram());
231 gl.enableVertexAttribArray(posLoc);
232 gl.vertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, &s_positions[0]);
233 gl.enable(GL_BLEND);
234 gl.blendFunc(GL_ONE, GL_ONE);
235 gl.blendEquation(GL_FUNC_ADD);
236 GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set up render state");
237 }
238
render(int numDrawCalls)239 void FlushFinishCase::render(int numDrawCalls)
240 {
241 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
242
243 const uint8_t indices[] = {0, 1, 2, 2, 1, 3};
244
245 gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
246
247 for (int ndx = 0; ndx < numDrawCalls; ndx++)
248 gl.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, &indices[0]);
249 }
250
readPixels(void)251 void FlushFinishCase::readPixels(void)
252 {
253 const glw::Functions &gl = m_context.getRenderContext().getFunctions();
254 uint8_t tmp[4];
255
256 gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &tmp);
257 }
258
calibrate(void)259 FlushFinishCase::CalibrationParams FlushFinishCase::calibrate(void)
260 {
261 tcu::ScopedLogSection section(m_testCtx.getLog(), "CalibrationInfo", "Calibration info");
262 CalibrationParams params;
263
264 // Find draw call count that results in desired maximum time.
265 {
266 uint64_t prevDuration = 0;
267 int prevDrawCount = 1;
268 int curDrawCount = 1;
269
270 m_testCtx.getLog() << TestLog::Message
271 << "Calibrating maximum draw call count, target duration = " << int(MAX_SAMPLE_DURATION_US)
272 << " us" << TestLog::EndMessage;
273
274 for (;;)
275 {
276 uint64_t curDuration;
277
278 {
279 const uint64_t startTime = deGetMicroseconds();
280 render(curDrawCount);
281 readPixels();
282 curDuration = deGetMicroseconds() - startTime;
283 }
284
285 m_testCtx.getLog() << TestLog::Message << "Duration with " << curDrawCount
286 << " draw calls = " << curDuration << " us" << TestLog::EndMessage;
287
288 if (curDuration > MAX_SAMPLE_DURATION_US)
289 {
290 if (curDrawCount > 1)
291 {
292 // Compute final count by using linear estimation.
293 const float a = float(curDuration - prevDuration) / float(curDrawCount - prevDrawCount);
294 const float b = float(prevDuration) - a * float(prevDrawCount);
295 const float est = (float(MAX_SAMPLE_DURATION_US) - b) / a;
296
297 curDrawCount = de::clamp(deFloorFloatToInt32(est), 1, int(MAX_DRAW_CALL_COUNT));
298 }
299 // else: Settle on 1.
300
301 break;
302 }
303 else if (curDrawCount >= MAX_DRAW_CALL_COUNT)
304 break; // Settle on maximum.
305 else
306 {
307 prevDrawCount = curDrawCount;
308 prevDuration = curDuration;
309 curDrawCount = curDrawCount * 2;
310 }
311 }
312
313 params.maxDrawCalls = curDrawCount;
314
315 m_testCtx.getLog() << TestLog::Integer("MaxDrawCalls", "Maximum number of draw calls", "", QP_KEY_TAG_NONE,
316 params.maxDrawCalls);
317 }
318
319 // Quick check.
320 if (params.maxDrawCalls < MIN_DRAW_CALL_COUNT)
321 throw CalibrationFailedException("Calibration failed, maximum draw call count is too low");
322
323 return params;
324 }
325
326 struct CompareSampleDrawCount
327 {
operator ()deqp::gles2::Functional::__anonc425510d0111::CompareSampleDrawCount328 bool operator()(const FlushFinishCase::Sample &a, const FlushFinishCase::Sample &b) const
329 {
330 return a.numDrawCalls < b.numDrawCalls;
331 }
332 };
333
getPointsFromSamples(const std::vector<FlushFinishCase::Sample> & samples,const uint64_t FlushFinishCase::Sample::* field)334 std::vector<Vec2> getPointsFromSamples(const std::vector<FlushFinishCase::Sample> &samples,
335 const uint64_t FlushFinishCase::Sample::*field)
336 {
337 vector<Vec2> points(samples.size());
338
339 for (size_t ndx = 0; ndx < samples.size(); ndx++)
340 points[ndx] = Vec2(float(samples[ndx].numDrawCalls), float(samples[ndx].*field));
341
342 return points;
343 }
344
345 template <typename T>
getMaximumValue(const std::vector<FlushFinishCase::Sample> & samples,const T FlushFinishCase::Sample::* field)346 T getMaximumValue(const std::vector<FlushFinishCase::Sample> &samples, const T FlushFinishCase::Sample::*field)
347 {
348 DE_ASSERT(!samples.empty());
349
350 T maxVal = samples[0].*field;
351
352 for (size_t ndx = 1; ndx < samples.size(); ndx++)
353 maxVal = de::max(maxVal, samples[ndx].*field);
354
355 return maxVal;
356 }
357
analyzeResults(const std::vector<Sample> & samples,const CalibrationParams & calibrationParams)358 void FlushFinishCase::analyzeResults(const std::vector<Sample> &samples, const CalibrationParams &calibrationParams)
359 {
360 const vector<Vec2> waitTimes = getPointsFromSamples(samples, &Sample::waitTime);
361 const vector<Vec2> readTimes = getPointsFromSamples(samples, &Sample::readPixelsTime);
362 const LineParameters waitLine = theilSenLinearRegression(waitTimes);
363 const LineParameters readLine = theilSenLinearRegression(readTimes);
364 const float normWaitCoef =
365 waitLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US);
366 const float normReadCoef =
367 readLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US);
368 bool allOk = true;
369
370 {
371 tcu::ScopedLogSection section(m_testCtx.getLog(), "Samples", "Samples");
372 vector<Sample> sortedSamples(samples.begin(), samples.end());
373
374 std::sort(sortedSamples.begin(), sortedSamples.end(), CompareSampleDrawCount());
375
376 for (vector<Sample>::const_iterator iter = sortedSamples.begin(); iter != sortedSamples.end(); ++iter)
377 m_testCtx.getLog() << *iter;
378 }
379
380 m_testCtx.getLog() << TestLog::Float("WaitCoefficient", "Wait coefficient", "", QP_KEY_TAG_NONE,
381 waitLine.coefficient)
382 << TestLog::Float("ReadCoefficient", "Read coefficient", "", QP_KEY_TAG_NONE,
383 readLine.coefficient)
384 << TestLog::Float("NormalizedWaitCoefficient", "Normalized wait coefficient", "",
385 QP_KEY_TAG_NONE, normWaitCoef)
386 << TestLog::Float("NormalizedReadCoefficient", "Normalized read coefficient", "",
387 QP_KEY_TAG_NONE, normReadCoef);
388
389 {
390 const bool waitCorrelated = normWaitCoef > CORRELATED_COEF_THRESHOLD;
391 const bool readCorrelated = normReadCoef > CORRELATED_COEF_THRESHOLD;
392 const bool waitNotCorr = normWaitCoef < NO_CORR_COEF_THRESHOLD;
393 const bool readNotCorr = normReadCoef < NO_CORR_COEF_THRESHOLD;
394
395 if (waitCorrelated || waitNotCorr)
396 m_testCtx.getLog() << TestLog::Message << "Wait time is" << (waitCorrelated ? "" : " NOT")
397 << " correlated to rendering workload size." << TestLog::EndMessage;
398 else
399 m_testCtx.getLog() << TestLog::Message
400 << "Warning: Wait time correlation to rendering workload size is unclear."
401 << TestLog::EndMessage;
402
403 if (readCorrelated || readNotCorr)
404 m_testCtx.getLog() << TestLog::Message << "Read time is" << (readCorrelated ? "" : " NOT")
405 << " correlated to rendering workload size." << TestLog::EndMessage;
406 else
407 m_testCtx.getLog() << TestLog::Message
408 << "Warning: Read time correlation to rendering workload size is unclear."
409 << TestLog::EndMessage;
410 }
411
412 for (int ndx = 0; ndx < 2; ndx++)
413 {
414 const float coef = ndx == 0 ? normWaitCoef : normReadCoef;
415 const char *name = ndx == 0 ? "wait" : "read";
416 const ExpectedBehavior behavior = ndx == 0 ? m_waitBehavior : m_readBehavior;
417 const float threshold = ndx == 0 ? m_waitThreshold : m_readThreshold;
418 const bool isOk = behavior == EXPECT_COEF_GREATER_THAN ? coef > threshold :
419 behavior == EXPECT_COEF_LESS_THAN ? coef < threshold :
420 false;
421 const char *cmpName = behavior == EXPECT_COEF_GREATER_THAN ? "greater than" :
422 behavior == EXPECT_COEF_LESS_THAN ? "less than" :
423 DE_NULL;
424
425 if (!isOk)
426 {
427 m_testCtx.getLog() << TestLog::Message << "ERROR: Expected " << name << " coefficient to be " << cmpName
428 << " " << threshold << TestLog::EndMessage;
429 allOk = false;
430 }
431 }
432
433 m_testCtx.setTestResult(allOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_COMPATIBILITY_WARNING,
434 allOk ? "Pass" : "Suspicious performance behavior");
435 }
436
iterate(void)437 FlushFinishCase::IterateResult FlushFinishCase::iterate(void)
438 {
439 vector<Sample> samples(NUM_SAMPLES);
440 CalibrationParams params;
441
442 tcu::warmupCPU();
443
444 setupRenderState();
445
446 // Do one full render cycle.
447 {
448 render(1);
449 readPixels();
450 }
451
452 // Calibrate.
453 try
454 {
455 params = calibrate();
456 }
457 catch (const CalibrationFailedException &e)
458 {
459 m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, e.what());
460 return STOP;
461 }
462
463 // Do measurement.
464 {
465 de::Random rnd(123);
466
467 for (size_t ndx = 0; ndx < samples.size(); ndx++)
468 {
469 const int drawCallCount = rnd.getInt(1, params.maxDrawCalls);
470 uint64_t waitStartTime;
471 uint64_t readStartTime;
472 uint64_t readFinishTime;
473
474 render(drawCallCount);
475
476 waitStartTime = deGetMicroseconds();
477 waitForGL();
478
479 readStartTime = deGetMicroseconds();
480 readPixels();
481 readFinishTime = deGetMicroseconds();
482
483 samples[ndx].numDrawCalls = drawCallCount;
484 samples[ndx].waitTime = readStartTime - waitStartTime;
485 samples[ndx].readPixelsTime = readFinishTime - readStartTime;
486
487 if (m_testCtx.getWatchDog())
488 qpWatchDog_touch(m_testCtx.getWatchDog());
489 }
490 }
491
492 // Analyze - sets test case result.
493 analyzeResults(samples, params);
494
495 return STOP;
496 }
497
498 class WaitOnlyCase : public FlushFinishCase
499 {
500 public:
WaitOnlyCase(Context & context)501 WaitOnlyCase(Context &context)
502 : FlushFinishCase(context, "wait", "Wait only", EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD,
503 EXPECT_COEF_GREATER_THAN, -1000.0f /* practically nothing is expected */)
504 {
505 }
506
init(void)507 void init(void)
508 {
509 m_testCtx.getLog() << TestLog::Message << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
510 FlushFinishCase::init();
511 }
512
513 protected:
waitForGL(void)514 void waitForGL(void)
515 {
516 busyWait(WAIT_TIME_MS);
517 }
518 };
519
520 class FlushOnlyCase : public FlushFinishCase
521 {
522 public:
FlushOnlyCase(Context & context)523 FlushOnlyCase(Context &context)
524 : FlushFinishCase(context, "flush", "Flush only", EXPECT_COEF_LESS_THAN, FLUSH_COEF_THRESHOLD,
525 EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD)
526 {
527 }
528
init(void)529 void init(void)
530 {
531 m_testCtx.getLog() << TestLog::Message << "Single call to glFlush()" << TestLog::EndMessage;
532 FlushFinishCase::init();
533 }
534
535 protected:
waitForGL(void)536 void waitForGL(void)
537 {
538 m_context.getRenderContext().getFunctions().flush();
539 }
540 };
541
542 class FlushWaitCase : public FlushFinishCase
543 {
544 public:
FlushWaitCase(Context & context)545 FlushWaitCase(Context &context)
546 : FlushFinishCase(context, "flush_wait", "Wait after flushing", EXPECT_COEF_LESS_THAN, FLUSH_COEF_THRESHOLD,
547 EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
548 {
549 }
550
init(void)551 void init(void)
552 {
553 m_testCtx.getLog() << TestLog::Message << "glFlush() followed by " << int(WAIT_TIME_MS) << " ms busy wait"
554 << TestLog::EndMessage;
555 FlushFinishCase::init();
556 }
557
558 protected:
waitForGL(void)559 void waitForGL(void)
560 {
561 m_context.getRenderContext().getFunctions().flush();
562 busyWait(WAIT_TIME_MS);
563 }
564 };
565
566 class FinishOnlyCase : public FlushFinishCase
567 {
568 public:
FinishOnlyCase(Context & context)569 FinishOnlyCase(Context &context)
570 : FlushFinishCase(context, "finish", "Finish only", EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD,
571 EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
572 {
573 }
574
init(void)575 void init(void)
576 {
577 m_testCtx.getLog() << TestLog::Message << "Single call to glFinish()" << TestLog::EndMessage;
578 FlushFinishCase::init();
579 }
580
581 protected:
waitForGL(void)582 void waitForGL(void)
583 {
584 m_context.getRenderContext().getFunctions().finish();
585 }
586 };
587
588 class FinishWaitCase : public FlushFinishCase
589 {
590 public:
FinishWaitCase(Context & context)591 FinishWaitCase(Context &context)
592 : FlushFinishCase(context, "finish_wait", "Finish and wait", EXPECT_COEF_GREATER_THAN,
593 CORRELATED_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
594 {
595 }
596
init(void)597 void init(void)
598 {
599 m_testCtx.getLog() << TestLog::Message << "glFinish() followed by " << int(WAIT_TIME_MS) << " ms busy wait"
600 << TestLog::EndMessage;
601 FlushFinishCase::init();
602 }
603
604 protected:
waitForGL(void)605 void waitForGL(void)
606 {
607 m_context.getRenderContext().getFunctions().finish();
608 busyWait(WAIT_TIME_MS);
609 }
610 };
611
612 } // namespace
613
FlushFinishTests(Context & context)614 FlushFinishTests::FlushFinishTests(Context &context) : TestCaseGroup(context, "flush_finish", "Flush and Finish tests")
615 {
616 }
617
~FlushFinishTests(void)618 FlushFinishTests::~FlushFinishTests(void)
619 {
620 }
621
init(void)622 void FlushFinishTests::init(void)
623 {
624 addChild(new WaitOnlyCase(m_context));
625 addChild(new FlushOnlyCase(m_context));
626 addChild(new FlushWaitCase(m_context));
627 addChild(new FinishOnlyCase(m_context));
628 addChild(new FinishWaitCase(m_context));
629 }
630
631 } // namespace Functional
632 } // namespace gles2
633 } // namespace deqp
634