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 48namespace deqp 49{ 50namespace gles2 51{ 52namespace Functional 53{ 54 55using std::vector; 56using std::string; 57using tcu::TestLog; 58using tcu::Vec2; 59using deqp::gls::theilSenLinearRegression; 60using deqp::gls::LineParameters; 61 62namespace 63{ 64 65enum 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 76const float NO_CORR_COEF_THRESHOLD = 0.1f; 77const float FLUSH_COEF_THRESHOLD = 0.2f; 78const float CORRELATED_COEF_THRESHOLD = 0.5f; 79 80static 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 95class CalibrationFailedException : public std::runtime_error 96{ 97public: 98 CalibrationFailedException (const std::string& reason) : std::runtime_error(reason) {} 99}; 100 101class FlushFinishCase : public TestCase 102{ 103public: 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 135protected: 136 virtual void waitForGL (void) = 0; 137 138private: 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 157FlushFinishCase::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 167FlushFinishCase::~FlushFinishCase (void) 168{ 169 FlushFinishCase::deinit(); 170} 171 172void 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 206void FlushFinishCase::deinit (void) 207{ 208 delete m_program; 209 m_program = DE_NULL; 210} 211 212tcu::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 218void 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 245void 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 257void 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 265FlushFinishCase::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 328struct CompareSampleDrawCount 329{ 330 bool operator() (const FlushFinishCase::Sample& a, const FlushFinishCase::Sample& b) const { return a.numDrawCalls < b.numDrawCalls; } 331}; 332 333std::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 343template<typename T> 344T 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 356void 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_FAIL, 417 allOk ? "Pass" : "Suspicious performance behavior"); 418} 419 420FlushFinishCase::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 481class WaitOnlyCase : public FlushFinishCase 482{ 483public: 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 495protected: 496 void waitForGL (void) 497 { 498 busyWait(WAIT_TIME_MS); 499 } 500}; 501 502class FlushOnlyCase : public FlushFinishCase 503{ 504public: 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 516protected: 517 void waitForGL (void) 518 { 519 m_context.getRenderContext().getFunctions().flush(); 520 } 521}; 522 523class FlushWaitCase : public FlushFinishCase 524{ 525public: 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 537protected: 538 void waitForGL (void) 539 { 540 m_context.getRenderContext().getFunctions().flush(); 541 busyWait(WAIT_TIME_MS); 542 } 543}; 544 545class FinishOnlyCase : public FlushFinishCase 546{ 547public: 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 559protected: 560 void waitForGL (void) 561 { 562 m_context.getRenderContext().getFunctions().finish(); 563 } 564}; 565 566class FinishWaitCase : public FlushFinishCase 567{ 568public: 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 580protected: 581 void waitForGL (void) 582 { 583 m_context.getRenderContext().getFunctions().finish(); 584 busyWait(WAIT_TIME_MS); 585 } 586}; 587 588} // anonymous 589 590FlushFinishTests::FlushFinishTests (Context& context) 591 : TestCaseGroup(context, "flush_finish", "Flush and Finish tests") 592{ 593} 594 595FlushFinishTests::~FlushFinishTests (void) 596{ 597} 598 599void 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