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 std::vector;
56 using std::string;
57 using tcu::TestLog;
58 using tcu::Vec2;
59 using deqp::gls::theilSenLinearRegression;
60 using deqp::gls::LineParameters;
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 deUint64 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 >= deUint64(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 class FlushFinishCase : public TestCase
102 {
103 public:
104 enum ExpectedBehavior
105 {
106 EXPECT_COEF_LESS_THAN = 0,
107 EXPECT_COEF_GREATER_THAN,
108 };
109
110 FlushFinishCase (Context& context,
111 const char* name,
112 const char* description,
113 ExpectedBehavior waitBehavior,
114 float waitThreshold,
115 ExpectedBehavior readBehavior,
116 float readThreshold);
117 ~FlushFinishCase (void);
118
119 void init (void);
120 void deinit (void);
121 IterateResult iterate (void);
122
123 struct Sample
124 {
125 int numDrawCalls;
126 deUint64 waitTime;
127 deUint64 readPixelsTime;
128 };
129
130 struct CalibrationParams
131 {
132 int maxDrawCalls;
133 };
134
135 protected:
136 virtual void waitForGL (void) = 0;
137
138 private:
139 FlushFinishCase (const FlushFinishCase&);
140 FlushFinishCase& operator= (const FlushFinishCase&);
141
142 CalibrationParams calibrate (void);
143 void analyzeResults (const std::vector<Sample>& samples, const CalibrationParams& calibrationParams);
144
145 void setupRenderState (void);
146 void render (int numDrawCalls);
147 void readPixels (void);
148
149 const ExpectedBehavior m_waitBehavior;
150 const float m_waitThreshold;
151 const ExpectedBehavior m_readBehavior;
152 const float m_readThreshold;
153
154 glu::ShaderProgram* m_program;
155 };
156
FlushFinishCase(Context & context,const char * name,const char * description,ExpectedBehavior waitBehavior,float waitThreshold,ExpectedBehavior readBehavior,float readThreshold)157 FlushFinishCase::FlushFinishCase (Context& context, const char* name, const char* description, ExpectedBehavior waitBehavior, float waitThreshold, ExpectedBehavior readBehavior, float readThreshold)
158 : TestCase (context, name, description)
159 , m_waitBehavior (waitBehavior)
160 , m_waitThreshold (waitThreshold)
161 , m_readBehavior (readBehavior)
162 , m_readThreshold (readThreshold)
163 , m_program (DE_NULL)
164 {
165 }
166
~FlushFinishCase(void)167 FlushFinishCase::~FlushFinishCase (void)
168 {
169 FlushFinishCase::deinit();
170 }
171
init(void)172 void FlushFinishCase::init (void)
173 {
174 DE_ASSERT(!m_program);
175
176 m_program = new glu::ShaderProgram(m_context.getRenderContext(),
177 glu::ProgramSources()
178 << glu::VertexSource(
179 "attribute highp vec4 a_position;\n"
180 "varying highp vec4 v_coord;\n"
181 "void main (void)\n"
182 "{\n"
183 " gl_Position = a_position;\n"
184 " v_coord = a_position;\n"
185 "}\n")
186 << glu::FragmentSource(
187 "uniform mediump int u_numIters;\n"
188 "varying mediump vec4 v_coord;\n"
189 "void main (void)\n"
190 "{\n"
191 " highp vec4 color = v_coord;\n"
192 " for (int i = 0; i < " + de::toString(int(NUM_ITERS_IN_SHADER)) + "; i++)\n"
193 " color = sin(color);\n"
194 " gl_FragColor = color;\n"
195 "}\n"));
196
197 if (!m_program->isOk())
198 {
199 m_testCtx.getLog() << *m_program;
200 delete m_program;
201 m_program = DE_NULL;
202 TCU_FAIL("Compile failed");
203 }
204 }
205
deinit(void)206 void FlushFinishCase::deinit (void)
207 {
208 delete m_program;
209 m_program = DE_NULL;
210 }
211
operator <<(tcu::TestLog & log,const FlushFinishCase::Sample & sample)212 tcu::TestLog& operator<< (tcu::TestLog& log, const FlushFinishCase::Sample& sample)
213 {
214 log << TestLog::Message << sample.numDrawCalls << " calls:\t" << sample.waitTime << " us wait,\t" << 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[] =
226 {
227 -1.0f, -1.0f,
228 +1.0f, -1.0f,
229 -1.0f, +1.0f,
230 +1.0f, +1.0f
231 };
232
233 TCU_CHECK(posLoc >= 0);
234
235 gl.viewport(0, 0, viewportW, viewportH);
236 gl.useProgram(m_program->getProgram());
237 gl.enableVertexAttribArray(posLoc);
238 gl.vertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, &s_positions[0]);
239 gl.enable(GL_BLEND);
240 gl.blendFunc(GL_ONE, GL_ONE);
241 gl.blendEquation(GL_FUNC_ADD);
242 GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set up render state");
243 }
244
render(int numDrawCalls)245 void FlushFinishCase::render (int numDrawCalls)
246 {
247 const glw::Functions& gl = m_context.getRenderContext().getFunctions();
248
249 const deUint8 indices[] = { 0, 1, 2, 2, 1, 3 };
250
251 gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
252
253 for (int ndx = 0; ndx < numDrawCalls; ndx++)
254 gl.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, &indices[0]);
255 }
256
readPixels(void)257 void FlushFinishCase::readPixels (void)
258 {
259 const glw::Functions& gl = m_context.getRenderContext().getFunctions();
260 deUint8 tmp[4];
261
262 gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &tmp);
263 }
264
calibrate(void)265 FlushFinishCase::CalibrationParams FlushFinishCase::calibrate (void)
266 {
267 tcu::ScopedLogSection section (m_testCtx.getLog(), "CalibrationInfo", "Calibration info");
268 CalibrationParams params;
269
270 // Find draw call count that results in desired maximum time.
271 {
272 deUint64 prevDuration = 0;
273 int prevDrawCount = 1;
274 int curDrawCount = 1;
275
276 m_testCtx.getLog() << TestLog::Message << "Calibrating maximum draw call count, target duration = " << int(MAX_SAMPLE_DURATION_US) << " us" << TestLog::EndMessage;
277
278 for (;;)
279 {
280 deUint64 curDuration;
281
282 {
283 const deUint64 startTime = deGetMicroseconds();
284 render(curDrawCount);
285 readPixels();
286 curDuration = deGetMicroseconds()-startTime;
287 }
288
289 m_testCtx.getLog() << TestLog::Message << "Duration with " << curDrawCount << " draw calls = " << curDuration << " us" << TestLog::EndMessage;
290
291 if (curDuration > MAX_SAMPLE_DURATION_US)
292 {
293 if (curDrawCount > 1)
294 {
295 // Compute final count by using linear estimation.
296 const float a = float(curDuration - prevDuration) / float(curDrawCount - prevDrawCount);
297 const float b = float(prevDuration) - a*float(prevDrawCount);
298 const float est = (float(MAX_SAMPLE_DURATION_US) - b) / a;
299
300 curDrawCount = de::clamp(deFloorFloatToInt32(est), 1, int(MAX_DRAW_CALL_COUNT));
301 }
302 // else: Settle on 1.
303
304 break;
305 }
306 else if (curDrawCount >= MAX_DRAW_CALL_COUNT)
307 break; // Settle on maximum.
308 else
309 {
310 prevDrawCount = curDrawCount;
311 prevDuration = curDuration;
312 curDrawCount = curDrawCount*2;
313 }
314 }
315
316 params.maxDrawCalls = curDrawCount;
317
318 m_testCtx.getLog() << TestLog::Integer("MaxDrawCalls", "Maximum number of draw calls", "", QP_KEY_TAG_NONE, params.maxDrawCalls);
319 }
320
321 // Sanity check.
322 if (params.maxDrawCalls < MIN_DRAW_CALL_COUNT)
323 throw CalibrationFailedException("Calibration failed, maximum draw call count is too low");
324
325 return params;
326 }
327
328 struct CompareSampleDrawCount
329 {
operator ()deqp::gles2::Functional::__anondce66fc20111::CompareSampleDrawCount330 bool operator() (const FlushFinishCase::Sample& a, const FlushFinishCase::Sample& b) const { return a.numDrawCalls < b.numDrawCalls; }
331 };
332
getPointsFromSamples(const std::vector<FlushFinishCase::Sample> & samples,const deUint64 FlushFinishCase::Sample::* field)333 std::vector<Vec2> getPointsFromSamples (const std::vector<FlushFinishCase::Sample>& samples, const deUint64 FlushFinishCase::Sample::*field)
334 {
335 vector<Vec2> points(samples.size());
336
337 for (size_t ndx = 0; ndx < samples.size(); ndx++)
338 points[ndx] = Vec2(float(samples[ndx].numDrawCalls), float(samples[ndx].*field));
339
340 return points;
341 }
342
343 template<typename T>
getMaximumValue(const std::vector<FlushFinishCase::Sample> & samples,const T FlushFinishCase::Sample::* field)344 T getMaximumValue (const std::vector<FlushFinishCase::Sample>& samples, const T FlushFinishCase::Sample::*field)
345 {
346 DE_ASSERT(!samples.empty());
347
348 T maxVal = samples[0].*field;
349
350 for (size_t ndx = 1; ndx < samples.size(); ndx++)
351 maxVal = de::max(maxVal, samples[ndx].*field);
352
353 return maxVal;
354 }
355
analyzeResults(const std::vector<Sample> & samples,const CalibrationParams & calibrationParams)356 void FlushFinishCase::analyzeResults (const std::vector<Sample>& samples, const CalibrationParams& calibrationParams)
357 {
358 const vector<Vec2> waitTimes = getPointsFromSamples(samples, &Sample::waitTime);
359 const vector<Vec2> readTimes = getPointsFromSamples(samples, &Sample::readPixelsTime);
360 const LineParameters waitLine = theilSenLinearRegression(waitTimes);
361 const LineParameters readLine = theilSenLinearRegression(readTimes);
362 const float normWaitCoef = waitLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US);
363 const float normReadCoef = readLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US);
364 bool allOk = true;
365
366 {
367 tcu::ScopedLogSection section (m_testCtx.getLog(), "Samples", "Samples");
368 vector<Sample> sortedSamples (samples.begin(), samples.end());
369
370 std::sort(sortedSamples.begin(), sortedSamples.end(), CompareSampleDrawCount());
371
372 for (vector<Sample>::const_iterator iter = sortedSamples.begin(); iter != sortedSamples.end(); ++iter)
373 m_testCtx.getLog() << *iter;
374 }
375
376 m_testCtx.getLog() << TestLog::Float("WaitCoefficient", "Wait coefficient", "", QP_KEY_TAG_NONE, waitLine.coefficient)
377 << TestLog::Float("ReadCoefficient", "Read coefficient", "", QP_KEY_TAG_NONE, readLine.coefficient)
378 << TestLog::Float("NormalizedWaitCoefficient", "Normalized wait coefficient", "", QP_KEY_TAG_NONE, normWaitCoef)
379 << TestLog::Float("NormalizedReadCoefficient", "Normalized read coefficient", "", QP_KEY_TAG_NONE, normReadCoef);
380
381 {
382 const bool waitCorrelated = normWaitCoef > CORRELATED_COEF_THRESHOLD;
383 const bool readCorrelated = normReadCoef > CORRELATED_COEF_THRESHOLD;
384 const bool waitNotCorr = normWaitCoef < NO_CORR_COEF_THRESHOLD;
385 const bool readNotCorr = normReadCoef < NO_CORR_COEF_THRESHOLD;
386
387 if (waitCorrelated || waitNotCorr)
388 m_testCtx.getLog() << TestLog::Message << "Wait time is" << (waitCorrelated ? "" : " NOT") << " correlated to rendering workload size." << TestLog::EndMessage;
389 else
390 m_testCtx.getLog() << TestLog::Message << "Warning: Wait time correlation to rendering workload size is unclear." << TestLog::EndMessage;
391
392 if (readCorrelated || readNotCorr)
393 m_testCtx.getLog() << TestLog::Message << "Read time is" << (readCorrelated ? "" : " NOT") << " correlated to rendering workload size." << TestLog::EndMessage;
394 else
395 m_testCtx.getLog() << TestLog::Message << "Warning: Read time correlation to rendering workload size is unclear." << TestLog::EndMessage;
396 }
397
398 for (int ndx = 0; ndx < 2; ndx++)
399 {
400 const float coef = ndx == 0 ? normWaitCoef : normReadCoef;
401 const char* name = ndx == 0 ? "wait" : "read";
402 const ExpectedBehavior behavior = ndx == 0 ? m_waitBehavior : m_readBehavior;
403 const float threshold = ndx == 0 ? m_waitThreshold : m_readThreshold;
404 const bool isOk = behavior == EXPECT_COEF_GREATER_THAN ? coef > threshold :
405 behavior == EXPECT_COEF_LESS_THAN ? coef < threshold : false;
406 const char* cmpName = behavior == EXPECT_COEF_GREATER_THAN ? "greater than" :
407 behavior == EXPECT_COEF_LESS_THAN ? "less than" : DE_NULL;
408
409 if (!isOk)
410 {
411 m_testCtx.getLog() << TestLog::Message << "ERROR: Expected " << name << " coefficient to be " << cmpName << " " << threshold << TestLog::EndMessage;
412 allOk = false;
413 }
414 }
415
416 m_testCtx.setTestResult(allOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_COMPATIBILITY_WARNING,
417 allOk ? "Pass" : "Suspicious performance behavior");
418 }
419
iterate(void)420 FlushFinishCase::IterateResult FlushFinishCase::iterate (void)
421 {
422 vector<Sample> samples (NUM_SAMPLES);
423 CalibrationParams params;
424
425 tcu::warmupCPU();
426
427 setupRenderState();
428
429 // Do one full render cycle.
430 {
431 render(1);
432 readPixels();
433 }
434
435 // Calibrate.
436 try
437 {
438 params = calibrate();
439 }
440 catch (const CalibrationFailedException& e)
441 {
442 m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, e.what());
443 return STOP;
444 }
445
446 // Do measurement.
447 {
448 de::Random rnd (123);
449
450 for (size_t ndx = 0; ndx < samples.size(); ndx++)
451 {
452 const int drawCallCount = rnd.getInt(1, params.maxDrawCalls);
453 deUint64 waitStartTime;
454 deUint64 readStartTime;
455 deUint64 readFinishTime;
456
457 render(drawCallCount);
458
459 waitStartTime = deGetMicroseconds();
460 waitForGL();
461
462 readStartTime = deGetMicroseconds();
463 readPixels();
464 readFinishTime = deGetMicroseconds();
465
466 samples[ndx].numDrawCalls = drawCallCount;
467 samples[ndx].waitTime = readStartTime-waitStartTime;
468 samples[ndx].readPixelsTime = readFinishTime-readStartTime;
469
470 if (m_testCtx.getWatchDog())
471 qpWatchDog_touch(m_testCtx.getWatchDog());
472 }
473 }
474
475 // Analyze - sets test case result.
476 analyzeResults(samples, params);
477
478 return STOP;
479 }
480
481 class WaitOnlyCase : public FlushFinishCase
482 {
483 public:
WaitOnlyCase(Context & context)484 WaitOnlyCase (Context& context)
485 : FlushFinishCase(context, "wait", "Wait only", EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD, EXPECT_COEF_GREATER_THAN, -1000.0f /* practically nothing is expected */)
486 {
487 }
488
init(void)489 void init (void)
490 {
491 m_testCtx.getLog() << TestLog::Message << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
492 FlushFinishCase::init();
493 }
494
495 protected:
waitForGL(void)496 void waitForGL (void)
497 {
498 busyWait(WAIT_TIME_MS);
499 }
500 };
501
502 class FlushOnlyCase : public FlushFinishCase
503 {
504 public:
FlushOnlyCase(Context & context)505 FlushOnlyCase (Context& context)
506 : FlushFinishCase(context, "flush", "Flush only", EXPECT_COEF_LESS_THAN, FLUSH_COEF_THRESHOLD, EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD)
507 {
508 }
509
init(void)510 void init (void)
511 {
512 m_testCtx.getLog() << TestLog::Message << "Single call to glFlush()" << TestLog::EndMessage;
513 FlushFinishCase::init();
514 }
515
516 protected:
waitForGL(void)517 void waitForGL (void)
518 {
519 m_context.getRenderContext().getFunctions().flush();
520 }
521 };
522
523 class FlushWaitCase : public FlushFinishCase
524 {
525 public:
FlushWaitCase(Context & context)526 FlushWaitCase (Context& context)
527 : FlushFinishCase(context, "flush_wait", "Wait after flushing", EXPECT_COEF_LESS_THAN, FLUSH_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
528 {
529 }
530
init(void)531 void init (void)
532 {
533 m_testCtx.getLog() << TestLog::Message << "glFlush() followed by " << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
534 FlushFinishCase::init();
535 }
536
537 protected:
waitForGL(void)538 void waitForGL (void)
539 {
540 m_context.getRenderContext().getFunctions().flush();
541 busyWait(WAIT_TIME_MS);
542 }
543 };
544
545 class FinishOnlyCase : public FlushFinishCase
546 {
547 public:
FinishOnlyCase(Context & context)548 FinishOnlyCase (Context& context)
549 : FlushFinishCase(context, "finish", "Finish only", EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
550 {
551 }
552
init(void)553 void init (void)
554 {
555 m_testCtx.getLog() << TestLog::Message << "Single call to glFinish()" << TestLog::EndMessage;
556 FlushFinishCase::init();
557 }
558
559 protected:
waitForGL(void)560 void waitForGL (void)
561 {
562 m_context.getRenderContext().getFunctions().finish();
563 }
564 };
565
566 class FinishWaitCase : public FlushFinishCase
567 {
568 public:
FinishWaitCase(Context & context)569 FinishWaitCase (Context& context)
570 : FlushFinishCase(context, "finish_wait", "Finish and wait", EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
571 {
572 }
573
init(void)574 void init (void)
575 {
576 m_testCtx.getLog() << TestLog::Message << "glFinish() followed by " << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
577 FlushFinishCase::init();
578 }
579
580 protected:
waitForGL(void)581 void waitForGL (void)
582 {
583 m_context.getRenderContext().getFunctions().finish();
584 busyWait(WAIT_TIME_MS);
585 }
586 };
587
588 } // anonymous
589
FlushFinishTests(Context & context)590 FlushFinishTests::FlushFinishTests (Context& context)
591 : TestCaseGroup(context, "flush_finish", "Flush and Finish tests")
592 {
593 }
594
~FlushFinishTests(void)595 FlushFinishTests::~FlushFinishTests (void)
596 {
597 }
598
init(void)599 void FlushFinishTests::init (void)
600 {
601 addChild(new WaitOnlyCase (m_context));
602 addChild(new FlushOnlyCase (m_context));
603 addChild(new FlushWaitCase (m_context));
604 addChild(new FinishOnlyCase (m_context));
605 addChild(new FinishWaitCase (m_context));
606 }
607
608 } // Functional
609 } // gles2
610 } // deqp
611