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 36 #include "glwEnums.hpp" 37 #include "glwFunctions.hpp" 38 39 #include "deRandom.hpp" 40 #include "deStringUtil.hpp" 41 #include "deClock.h" 42 #include "deThread.h" 43 #include "deMath.h" 44 45 #include <algorithm> 46 47 namespace deqp 48 { 49 namespace gles2 50 { 51 namespace Functional 52 { 53 54 using std::vector; 55 using std::string; 56 using tcu::TestLog; 57 using tcu::Vec2; 58 using deqp::gls::theilSenLinearRegression; 59 using deqp::gls::LineParameters; 60 61 namespace 62 { 63 64 enum 65 { 66 MAX_VIEWPORT_SIZE = 128, 67 MAX_SAMPLE_DURATION_US = 1000*1000, 68 WAIT_TIME_MS = 1200, 69 NUM_SAMPLES = 25, 70 MIN_DRAW_CALL_COUNT = 10, 71 MAX_DRAW_CALL_COUNT = 1<<20, 72 NUM_ITERS_IN_SHADER = 10 73 }; 74 75 const float NO_CORR_COEF_THRESHOLD = 0.1f; 76 const float FLUSH_COEF_THRESHOLD = 0.2f; 77 const float CORRELATED_COEF_THRESHOLD = 0.5f; 78 79 static void busyWait (int milliseconds) 80 { 81 const deUint64 startTime = deGetMicroseconds(); 82 float v = 2.0f; 83 84 for (;;) 85 { 86 for (int i = 0; i < 10; i++) 87 v = deFloatSin(v); 88 89 if (deGetMicroseconds()-startTime >= deUint64(1000*milliseconds)) 90 break; 91 } 92 } 93 94 class CalibrationFailedException : public std::runtime_error 95 { 96 public: 97 CalibrationFailedException (const std::string& reason) : std::runtime_error(reason) {} 98 }; 99 100 class FlushFinishCase : public TestCase 101 { 102 public: 103 enum ExpectedBehavior 104 { 105 EXPECT_COEF_LESS_THAN = 0, 106 EXPECT_COEF_GREATER_THAN, 107 }; 108 109 FlushFinishCase (Context& context, 110 const char* name, 111 const char* description, 112 ExpectedBehavior waitBehavior, 113 float waitThreshold, 114 ExpectedBehavior readBehavior, 115 float readThreshold); 116 ~FlushFinishCase (void); 117 118 void init (void); 119 void deinit (void); 120 IterateResult iterate (void); 121 122 struct Sample 123 { 124 int numDrawCalls; 125 deUint64 waitTime; 126 deUint64 readPixelsTime; 127 }; 128 129 struct CalibrationParams 130 { 131 int maxDrawCalls; 132 }; 133 134 protected: 135 virtual void waitForGL (void) = 0; 136 137 private: 138 FlushFinishCase (const FlushFinishCase&); 139 FlushFinishCase& operator= (const FlushFinishCase&); 140 141 CalibrationParams calibrate (void); 142 void analyzeResults (const std::vector<Sample>& samples, const CalibrationParams& calibrationParams); 143 144 void setupRenderState (void); 145 void render (int numDrawCalls); 146 void readPixels (void); 147 148 const ExpectedBehavior m_waitBehavior; 149 const float m_waitThreshold; 150 const ExpectedBehavior m_readBehavior; 151 const float m_readThreshold; 152 153 glu::ShaderProgram* m_program; 154 }; 155 156 FlushFinishCase::FlushFinishCase (Context& context, const char* name, const char* description, ExpectedBehavior waitBehavior, float waitThreshold, ExpectedBehavior readBehavior, 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 166 FlushFinishCase::~FlushFinishCase (void) 167 { 168 FlushFinishCase::deinit(); 169 } 170 171 void FlushFinishCase::init (void) 172 { 173 DE_ASSERT(!m_program); 174 175 m_program = new glu::ShaderProgram(m_context.getRenderContext(), 176 glu::ProgramSources() 177 << glu::VertexSource( 178 "attribute highp vec4 a_position;\n" 179 "varying highp vec4 v_coord;\n" 180 "void main (void)\n" 181 "{\n" 182 " gl_Position = a_position;\n" 183 " v_coord = a_position;\n" 184 "}\n") 185 << glu::FragmentSource( 186 "uniform mediump int u_numIters;\n" 187 "varying mediump vec4 v_coord;\n" 188 "void main (void)\n" 189 "{\n" 190 " highp vec4 color = v_coord;\n" 191 " for (int i = 0; i < " + de::toString(int(NUM_ITERS_IN_SHADER)) + "; 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 205 void FlushFinishCase::deinit (void) 206 { 207 delete m_program; 208 m_program = DE_NULL; 209 } 210 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" << sample.readPixelsTime << " us read" << TestLog::EndMessage; 214 return log; 215 } 216 217 void FlushFinishCase::setupRenderState (void) 218 { 219 const glw::Functions& gl = m_context.getRenderContext().getFunctions(); 220 const int posLoc = gl.getAttribLocation(m_program->getProgram(), "a_position"); 221 const int viewportW = de::min<int>(m_context.getRenderTarget().getWidth(), MAX_VIEWPORT_SIZE); 222 const int viewportH = de::min<int>(m_context.getRenderTarget().getHeight(), MAX_VIEWPORT_SIZE); 223 224 static const float s_positions[] = 225 { 226 -1.0f, -1.0f, 227 +1.0f, -1.0f, 228 -1.0f, +1.0f, 229 +1.0f, +1.0f 230 }; 231 232 TCU_CHECK(posLoc >= 0); 233 234 gl.viewport(0, 0, viewportW, viewportH); 235 gl.useProgram(m_program->getProgram()); 236 gl.enableVertexAttribArray(posLoc); 237 gl.vertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, &s_positions[0]); 238 gl.enable(GL_BLEND); 239 gl.blendFunc(GL_ONE, GL_ONE); 240 gl.blendEquation(GL_FUNC_ADD); 241 GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set up render state"); 242 } 243 244 void FlushFinishCase::render (int numDrawCalls) 245 { 246 const glw::Functions& gl = m_context.getRenderContext().getFunctions(); 247 248 const deUint8 indices[] = { 0, 1, 2, 2, 1, 3 }; 249 250 gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT); 251 252 for (int ndx = 0; ndx < numDrawCalls; ndx++) 253 gl.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, &indices[0]); 254 } 255 256 void FlushFinishCase::readPixels (void) 257 { 258 const glw::Functions& gl = m_context.getRenderContext().getFunctions(); 259 deUint8 tmp[4]; 260 261 gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &tmp); 262 } 263 264 FlushFinishCase::CalibrationParams FlushFinishCase::calibrate (void) 265 { 266 tcu::ScopedLogSection section (m_testCtx.getLog(), "CalibrationInfo", "Calibration info"); 267 CalibrationParams params; 268 269 // Find draw call count that results in desired maximum time. 270 { 271 deUint64 prevDuration = 0; 272 int prevDrawCount = 1; 273 int curDrawCount = 1; 274 275 m_testCtx.getLog() << TestLog::Message << "Calibrating maximum draw call count, target duration = " << int(MAX_SAMPLE_DURATION_US) << " us" << TestLog::EndMessage; 276 277 for (;;) 278 { 279 deUint64 curDuration; 280 281 { 282 const deUint64 startTime = deGetMicroseconds(); 283 render(curDrawCount); 284 readPixels(); 285 curDuration = deGetMicroseconds()-startTime; 286 } 287 288 m_testCtx.getLog() << TestLog::Message << "Duration with " << curDrawCount << " draw calls = " << curDuration << " us" << TestLog::EndMessage; 289 290 if (curDuration > MAX_SAMPLE_DURATION_US) 291 { 292 if (curDrawCount > 1) 293 { 294 // Compute final count by using linear estimation. 295 const float a = float(curDuration - prevDuration) / float(curDrawCount - prevDrawCount); 296 const float b = float(prevDuration) - a*float(prevDrawCount); 297 const float est = (float(MAX_SAMPLE_DURATION_US) - b) / a; 298 299 curDrawCount = de::clamp(deFloorFloatToInt32(est), 1, int(MAX_DRAW_CALL_COUNT)); 300 } 301 // else: Settle on 1. 302 303 break; 304 } 305 else if (curDrawCount >= MAX_DRAW_CALL_COUNT) 306 break; // Settle on maximum. 307 else 308 { 309 prevDrawCount = curDrawCount; 310 prevDuration = curDuration; 311 curDrawCount = curDrawCount*2; 312 } 313 } 314 315 params.maxDrawCalls = curDrawCount; 316 317 m_testCtx.getLog() << TestLog::Integer("MaxDrawCalls", "Maximum number of draw calls", "", QP_KEY_TAG_NONE, params.maxDrawCalls); 318 } 319 320 // Sanity check. 321 if (params.maxDrawCalls < MIN_DRAW_CALL_COUNT) 322 throw CalibrationFailedException("Calibration failed, maximum draw call count is too low"); 323 324 return params; 325 } 326 327 struct CompareSampleDrawCount 328 { 329 bool operator() (const FlushFinishCase::Sample& a, const FlushFinishCase::Sample& b) const { return a.numDrawCalls < b.numDrawCalls; } 330 }; 331 332 std::vector<Vec2> getPointsFromSamples (const std::vector<FlushFinishCase::Sample>& samples, const deUint64 FlushFinishCase::Sample::*field) 333 { 334 vector<Vec2> points(samples.size()); 335 336 for (size_t ndx = 0; ndx < samples.size(); ndx++) 337 points[ndx] = Vec2(float(samples[ndx].numDrawCalls), float(samples[ndx].*field)); 338 339 return points; 340 } 341 342 template<typename T> 343 T getMaximumValue (const std::vector<FlushFinishCase::Sample>& samples, const T FlushFinishCase::Sample::*field) 344 { 345 DE_ASSERT(!samples.empty()); 346 347 T maxVal = samples[0].*field; 348 349 for (size_t ndx = 1; ndx < samples.size(); ndx++) 350 maxVal = de::max(maxVal, samples[ndx].*field); 351 352 return maxVal; 353 } 354 355 void FlushFinishCase::analyzeResults (const std::vector<Sample>& samples, const CalibrationParams& calibrationParams) 356 { 357 const vector<Vec2> waitTimes = getPointsFromSamples(samples, &Sample::waitTime); 358 const vector<Vec2> readTimes = getPointsFromSamples(samples, &Sample::readPixelsTime); 359 const LineParameters waitLine = theilSenLinearRegression(waitTimes); 360 const LineParameters readLine = theilSenLinearRegression(readTimes); 361 const float normWaitCoef = waitLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US); 362 const float normReadCoef = readLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US); 363 bool allOk = true; 364 365 { 366 tcu::ScopedLogSection section (m_testCtx.getLog(), "Samples", "Samples"); 367 vector<Sample> sortedSamples (samples.begin(), samples.end()); 368 369 std::sort(sortedSamples.begin(), sortedSamples.end(), CompareSampleDrawCount()); 370 371 for (vector<Sample>::const_iterator iter = sortedSamples.begin(); iter != sortedSamples.end(); ++iter) 372 m_testCtx.getLog() << *iter; 373 } 374 375 m_testCtx.getLog() << TestLog::Float("WaitCoefficient", "Wait coefficient", "", QP_KEY_TAG_NONE, waitLine.coefficient) 376 << TestLog::Float("ReadCoefficient", "Read coefficient", "", QP_KEY_TAG_NONE, readLine.coefficient) 377 << TestLog::Float("NormalizedWaitCoefficient", "Normalized wait coefficient", "", QP_KEY_TAG_NONE, normWaitCoef) 378 << TestLog::Float("NormalizedReadCoefficient", "Normalized read coefficient", "", QP_KEY_TAG_NONE, normReadCoef); 379 380 { 381 const bool waitCorrelated = normWaitCoef > CORRELATED_COEF_THRESHOLD; 382 const bool readCorrelated = normReadCoef > CORRELATED_COEF_THRESHOLD; 383 const bool waitNotCorr = normWaitCoef < NO_CORR_COEF_THRESHOLD; 384 const bool readNotCorr = normReadCoef < NO_CORR_COEF_THRESHOLD; 385 386 if (waitCorrelated || waitNotCorr) 387 m_testCtx.getLog() << TestLog::Message << "Wait time is" << (waitCorrelated ? "" : " NOT") << " correlated to rendering workload size." << TestLog::EndMessage; 388 else 389 m_testCtx.getLog() << TestLog::Message << "Warning: Wait time correlation to rendering workload size is unclear." << TestLog::EndMessage; 390 391 if (readCorrelated || readNotCorr) 392 m_testCtx.getLog() << TestLog::Message << "Read time is" << (readCorrelated ? "" : " NOT") << " correlated to rendering workload size." << TestLog::EndMessage; 393 else 394 m_testCtx.getLog() << TestLog::Message << "Warning: Read time correlation to rendering workload size is unclear." << TestLog::EndMessage; 395 } 396 397 for (int ndx = 0; ndx < 2; ndx++) 398 { 399 const float coef = ndx == 0 ? normWaitCoef : normReadCoef; 400 const char* name = ndx == 0 ? "wait" : "read"; 401 const ExpectedBehavior behavior = ndx == 0 ? m_waitBehavior : m_readBehavior; 402 const float threshold = ndx == 0 ? m_waitThreshold : m_readThreshold; 403 const bool isOk = behavior == EXPECT_COEF_GREATER_THAN ? coef > threshold : 404 behavior == EXPECT_COEF_LESS_THAN ? coef < threshold : false; 405 const char* cmpName = behavior == EXPECT_COEF_GREATER_THAN ? "greater than" : 406 behavior == EXPECT_COEF_LESS_THAN ? "less than" : DE_NULL; 407 408 if (!isOk) 409 { 410 m_testCtx.getLog() << TestLog::Message << "ERROR: Expected " << name << " coefficient to be " << cmpName << " " << threshold << TestLog::EndMessage; 411 allOk = false; 412 } 413 } 414 415 m_testCtx.setTestResult(allOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, 416 allOk ? "Pass" : "Suspicious performance behavior"); 417 } 418 419 FlushFinishCase::IterateResult FlushFinishCase::iterate (void) 420 { 421 vector<Sample> samples (NUM_SAMPLES); 422 CalibrationParams params; 423 424 // Try to poke CPU into full speed. \todo [2013-12-26 pyry] Use more robust method. 425 busyWait(10); 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: 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 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: 496 void waitForGL (void) 497 { 498 busyWait(WAIT_TIME_MS); 499 } 500 }; 501 502 class FlushOnlyCase : public FlushFinishCase 503 { 504 public: 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 510 void init (void) 511 { 512 m_testCtx.getLog() << TestLog::Message << "Single call to glFlush()" << TestLog::EndMessage; 513 FlushFinishCase::init(); 514 } 515 516 protected: 517 void waitForGL (void) 518 { 519 m_context.getRenderContext().getFunctions().flush(); 520 } 521 }; 522 523 class FlushWaitCase : public FlushFinishCase 524 { 525 public: 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 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: 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: 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 553 void init (void) 554 { 555 m_testCtx.getLog() << TestLog::Message << "Single call to glFinish()" << TestLog::EndMessage; 556 FlushFinishCase::init(); 557 } 558 559 protected: 560 void waitForGL (void) 561 { 562 m_context.getRenderContext().getFunctions().finish(); 563 } 564 }; 565 566 class FinishWaitCase : public FlushFinishCase 567 { 568 public: 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 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: 581 void waitForGL (void) 582 { 583 m_context.getRenderContext().getFunctions().finish(); 584 busyWait(WAIT_TIME_MS); 585 } 586 }; 587 588 } // anonymous 589 590 FlushFinishTests::FlushFinishTests (Context& context) 591 : TestCaseGroup(context, "flush_finish", "Flush and Finish tests") 592 { 593 } 594 595 FlushFinishTests::~FlushFinishTests (void) 596 { 597 } 598 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