1 /*------------------------------------------------------------------------- 2 * drawElements Quality Program OpenGL (ES) 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 Calibration tools. 22 *//*--------------------------------------------------------------------*/ 23 24 #include "glsCalibration.hpp" 25 #include "tcuTestLog.hpp" 26 #include "tcuVectorUtil.hpp" 27 #include "deStringUtil.hpp" 28 #include "deMath.h" 29 #include "deClock.h" 30 31 #include <algorithm> 32 #include <limits> 33 34 using std::string; 35 using std::vector; 36 using tcu::Vec2; 37 using tcu::TestLog; 38 using tcu::TestNode; 39 using namespace glu; 40 41 namespace deqp 42 { 43 namespace gls 44 { 45 46 // Reorders input arbitrarily, linear complexity and no allocations 47 template<typename T> 48 float destructiveMedian (vector<T>& data) 49 { 50 const typename vector<T>::iterator mid = data.begin()+data.size()/2; 51 52 std::nth_element(data.begin(), mid, data.end()); 53 54 if (data.size()%2 == 0) // Even number of elements, need average of two centermost elements 55 return (*mid + *std::max_element(data.begin(), mid))*0.5f; // Data is partially sorted around mid, mid is half an item after center 56 else 57 return *mid; 58 } 59 60 LineParameters theilSenLinearRegression (const std::vector<tcu::Vec2>& dataPoints) 61 { 62 const float epsilon = 1e-6f; 63 64 const int numDataPoints = (int)dataPoints.size(); 65 vector<float> pairwiseCoefficients; 66 vector<float> pointwiseOffsets; 67 LineParameters result (0.0f, 0.0f); 68 69 // Compute the pairwise coefficients. 70 for (int i = 0; i < numDataPoints; i++) 71 { 72 const Vec2& ptA = dataPoints[i]; 73 74 for (int j = 0; j < i; j++) 75 { 76 const Vec2& ptB = dataPoints[j]; 77 78 if (de::abs(ptA.x() - ptB.x()) > epsilon) 79 pairwiseCoefficients.push_back((ptA.y() - ptB.y()) / (ptA.x() - ptB.x())); 80 } 81 } 82 83 // Find the median of the pairwise coefficients. 84 // \note If there are no data point pairs with differing x values, the coefficient variable will stay zero as initialized. 85 if (!pairwiseCoefficients.empty()) 86 result.coefficient = destructiveMedian(pairwiseCoefficients); 87 88 // Compute the offsets corresponding to the median coefficient, for all data points. 89 for (int i = 0; i < numDataPoints; i++) 90 pointwiseOffsets.push_back(dataPoints[i].y() - result.coefficient*dataPoints[i].x()); 91 92 // Find the median of the offsets. 93 // \note If there are no data points, the offset variable will stay zero as initialized. 94 if (!pointwiseOffsets.empty()) 95 result.offset = destructiveMedian(pointwiseOffsets); 96 97 return result; 98 } 99 100 // Sample from given values using linear interpolation at a given position as if values were laid to range [0, 1] 101 template <typename T> 102 static float linearSample (const std::vector<T>& values, float position) 103 { 104 DE_ASSERT(position >= 0.0f); 105 DE_ASSERT(position <= 1.0f); 106 107 const int maxNdx = (int)values.size() - 1; 108 const float floatNdx = (float)maxNdx * position; 109 const int lowerNdx = (int)deFloatFloor(floatNdx); 110 const int higherNdx = lowerNdx + (lowerNdx == maxNdx ? 0 : 1); // Use only last element if position is 1.0 111 const float interpolationFactor = floatNdx - (float)lowerNdx; 112 113 DE_ASSERT(lowerNdx >= 0 && lowerNdx < (int)values.size()); 114 DE_ASSERT(higherNdx >= 0 && higherNdx < (int)values.size()); 115 DE_ASSERT(interpolationFactor >= 0 && interpolationFactor < 1.0f); 116 117 return tcu::mix((float)values[lowerNdx], (float)values[higherNdx], interpolationFactor); 118 } 119 120 LineParametersWithConfidence theilSenSiegelLinearRegression (const std::vector<tcu::Vec2>& dataPoints, float reportedConfidence) 121 { 122 DE_ASSERT(!dataPoints.empty()); 123 124 // Siegel's variation 125 126 const float epsilon = 1e-6f; 127 const int numDataPoints = (int)dataPoints.size(); 128 std::vector<float> medianSlopes; 129 std::vector<float> pointwiseOffsets; 130 LineParametersWithConfidence result; 131 132 // Compute the median slope via each element 133 for (int i = 0; i < numDataPoints; i++) 134 { 135 const tcu::Vec2& ptA = dataPoints[i]; 136 std::vector<float> slopes; 137 138 slopes.reserve(numDataPoints); 139 140 for (int j = 0; j < numDataPoints; j++) 141 { 142 const tcu::Vec2& ptB = dataPoints[j]; 143 144 if (de::abs(ptA.x() - ptB.x()) > epsilon) 145 slopes.push_back((ptA.y() - ptB.y()) / (ptA.x() - ptB.x())); 146 } 147 148 // Add median of slopes through point i 149 medianSlopes.push_back(destructiveMedian(slopes)); 150 } 151 152 DE_ASSERT(!medianSlopes.empty()); 153 154 // Find the median of the pairwise coefficients. 155 std::sort(medianSlopes.begin(), medianSlopes.end()); 156 result.coefficient = linearSample(medianSlopes, 0.5f); 157 158 // Compute the offsets corresponding to the median coefficient, for all data points. 159 for (int i = 0; i < numDataPoints; i++) 160 pointwiseOffsets.push_back(dataPoints[i].y() - result.coefficient*dataPoints[i].x()); 161 162 // Find the median of the offsets. 163 std::sort(pointwiseOffsets.begin(), pointwiseOffsets.end()); 164 result.offset = linearSample(pointwiseOffsets, 0.5f); 165 166 // calculate confidence intervals 167 result.coefficientConfidenceLower = linearSample(medianSlopes, 0.5f - reportedConfidence*0.5f); 168 result.coefficientConfidenceUpper = linearSample(medianSlopes, 0.5f + reportedConfidence*0.5f); 169 170 result.offsetConfidenceLower = linearSample(pointwiseOffsets, 0.5f - reportedConfidence*0.5f); 171 result.offsetConfidenceUpper = linearSample(pointwiseOffsets, 0.5f + reportedConfidence*0.5f); 172 173 result.confidence = reportedConfidence; 174 175 return result; 176 } 177 178 bool MeasureState::isDone (void) const 179 { 180 return (int)frameTimes.size() >= maxNumFrames || (frameTimes.size() >= 2 && 181 frameTimes[frameTimes.size()-2] >= (deUint64)frameShortcutTime && 182 frameTimes[frameTimes.size()-1] >= (deUint64)frameShortcutTime); 183 } 184 185 deUint64 MeasureState::getTotalTime (void) const 186 { 187 deUint64 time = 0; 188 for (int i = 0; i < (int)frameTimes.size(); i++) 189 time += frameTimes[i]; 190 return time; 191 } 192 193 void MeasureState::clear (void) 194 { 195 maxNumFrames = 0; 196 frameShortcutTime = std::numeric_limits<float>::infinity(); 197 numDrawCalls = 0; 198 frameTimes.clear(); 199 } 200 201 void MeasureState::start (int maxNumFrames_, float frameShortcutTime_, int numDrawCalls_) 202 { 203 frameTimes.clear(); 204 frameTimes.reserve(maxNumFrames_); 205 maxNumFrames = maxNumFrames_; 206 frameShortcutTime = frameShortcutTime_; 207 numDrawCalls = numDrawCalls_; 208 } 209 210 TheilSenCalibrator::TheilSenCalibrator (void) 211 : m_params (1 /* initial calls */, 10 /* calibrate iter frames */, 2000.0f /* calibrate iter shortcut threshold */, 31 /* max calibration iterations */, 212 1000.0f/30.0f /* target frame time */, 1000.0f/60.0f /* frame time cap */, 1000.0f /* target measure duration */) 213 , m_state (INTERNALSTATE_LAST) 214 { 215 clear(); 216 } 217 218 TheilSenCalibrator::TheilSenCalibrator (const CalibratorParameters& params) 219 : m_params (params) 220 , m_state (INTERNALSTATE_LAST) 221 { 222 clear(); 223 } 224 225 TheilSenCalibrator::~TheilSenCalibrator() 226 { 227 } 228 229 void TheilSenCalibrator::clear (void) 230 { 231 m_measureState.clear(); 232 m_calibrateIterations.clear(); 233 m_state = INTERNALSTATE_CALIBRATING; 234 } 235 236 void TheilSenCalibrator::clear (const CalibratorParameters& params) 237 { 238 m_params = params; 239 clear(); 240 } 241 242 TheilSenCalibrator::State TheilSenCalibrator::getState (void) const 243 { 244 if (m_state == INTERNALSTATE_FINISHED) 245 return STATE_FINISHED; 246 else 247 { 248 DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING || !m_measureState.isDone()); 249 return m_measureState.isDone() ? STATE_RECOMPUTE_PARAMS : STATE_MEASURE; 250 } 251 } 252 253 void TheilSenCalibrator::recordIteration (deUint64 iterationTime) 254 { 255 DE_ASSERT((m_state == INTERNALSTATE_CALIBRATING || m_state == INTERNALSTATE_RUNNING) && !m_measureState.isDone()); 256 m_measureState.frameTimes.push_back(iterationTime); 257 258 if (m_state == INTERNALSTATE_RUNNING && m_measureState.isDone()) 259 m_state = INTERNALSTATE_FINISHED; 260 } 261 262 void TheilSenCalibrator::recomputeParameters (void) 263 { 264 DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING); 265 DE_ASSERT(m_measureState.isDone()); 266 267 // Minimum and maximum acceptable frame times. 268 const float minGoodFrameTimeUs = m_params.targetFrameTimeUs * 0.95f; 269 const float maxGoodFrameTimeUs = m_params.targetFrameTimeUs * 1.15f; 270 271 const int numIterations = (int)m_calibrateIterations.size(); 272 273 // Record frame time. 274 if (numIterations > 0) 275 { 276 m_calibrateIterations.back().frameTime = (float)((double)m_measureState.getTotalTime() / (double)m_measureState.frameTimes.size()); 277 278 // Check if we're good enough to stop calibrating. 279 { 280 bool endCalibration = false; 281 282 // Is the maximum calibration iteration limit reached? 283 endCalibration = endCalibration || (int)m_calibrateIterations.size() >= m_params.maxCalibrateIterations; 284 285 // Do a few past iterations have frame time in acceptable range? 286 { 287 const int numRelevantPastIterations = 2; 288 289 if (!endCalibration && (int)m_calibrateIterations.size() >= numRelevantPastIterations) 290 { 291 const CalibrateIteration* const past = &m_calibrateIterations[m_calibrateIterations.size() - numRelevantPastIterations]; 292 bool allInGoodRange = true; 293 294 for (int i = 0; i < numRelevantPastIterations && allInGoodRange; i++) 295 { 296 const float frameTimeUs = past[i].frameTime; 297 if (!de::inRange(frameTimeUs, minGoodFrameTimeUs, maxGoodFrameTimeUs)) 298 allInGoodRange = false; 299 } 300 301 endCalibration = endCalibration || allInGoodRange; 302 } 303 } 304 305 // Do a few past iterations have similar-enough call counts? 306 { 307 const int numRelevantPastIterations = 3; 308 if (!endCalibration && (int)m_calibrateIterations.size() >= numRelevantPastIterations) 309 { 310 const CalibrateIteration* const past = &m_calibrateIterations[m_calibrateIterations.size() - numRelevantPastIterations]; 311 int minCallCount = std::numeric_limits<int>::max(); 312 int maxCallCount = std::numeric_limits<int>::min(); 313 314 for (int i = 0; i < numRelevantPastIterations; i++) 315 { 316 minCallCount = de::min(minCallCount, past[i].numDrawCalls); 317 maxCallCount = de::max(maxCallCount, past[i].numDrawCalls); 318 } 319 320 if ((float)(maxCallCount - minCallCount) <= (float)minCallCount * 0.1f) 321 endCalibration = true; 322 } 323 } 324 325 // Is call count just 1, and frame time still way too high? 326 endCalibration = endCalibration || (m_calibrateIterations.back().numDrawCalls == 1 && m_calibrateIterations.back().frameTime > m_params.targetFrameTimeUs*2.0f); 327 328 if (endCalibration) 329 { 330 const int minFrames = 10; 331 const int maxFrames = 60; 332 int numMeasureFrames = deClamp32(deRoundFloatToInt32(m_params.targetMeasureDurationUs / m_calibrateIterations.back().frameTime), minFrames, maxFrames); 333 334 m_state = INTERNALSTATE_RUNNING; 335 m_measureState.start(numMeasureFrames, m_params.calibrateIterationShortcutThreshold, m_calibrateIterations.back().numDrawCalls); 336 return; 337 } 338 } 339 } 340 341 DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING); 342 343 // Estimate new call count. 344 { 345 int newCallCount; 346 347 if (numIterations == 0) 348 newCallCount = m_params.numInitialCalls; 349 else 350 { 351 vector<Vec2> dataPoints; 352 for (int i = 0; i < numIterations; i++) 353 { 354 if (m_calibrateIterations[i].numDrawCalls == 1 || m_calibrateIterations[i].frameTime > m_params.frameTimeCapUs*1.05f) // Only account for measurements not too near the cap. 355 dataPoints.push_back(Vec2((float)m_calibrateIterations[i].numDrawCalls, m_calibrateIterations[i].frameTime)); 356 } 357 358 if (numIterations == 1) 359 dataPoints.push_back(Vec2(0.0f, 0.0f)); // If there's just one measurement so far, this will help in getting the next estimate. 360 361 { 362 const float targetFrameTimeUs = m_params.targetFrameTimeUs; 363 const float coeffEpsilon = 0.001f; // Coefficient must be large enough (and positive) to be considered sensible. 364 365 const LineParameters estimatorLine = theilSenLinearRegression(dataPoints); 366 367 int prevMaxCalls = 0; 368 369 // Find the maximum of the past call counts. 370 for (int i = 0; i < numIterations; i++) 371 prevMaxCalls = de::max(prevMaxCalls, m_calibrateIterations[i].numDrawCalls); 372 373 if (estimatorLine.coefficient < coeffEpsilon) // Coefficient not good for sensible estimation; increase call count enough to get a reasonably different value. 374 newCallCount = 2*prevMaxCalls; 375 else 376 { 377 // Solve newCallCount such that approximately targetFrameTime = offset + coefficient*newCallCount. 378 newCallCount = (int)((targetFrameTimeUs - estimatorLine.offset) / estimatorLine.coefficient + 0.5f); 379 380 // We should generally prefer FPS counts below the target rather than above (i.e. higher frame times rather than lower). 381 if (estimatorLine.offset + estimatorLine.coefficient*(float)newCallCount < minGoodFrameTimeUs) 382 newCallCount++; 383 } 384 385 // Make sure we have at least minimum amount of calls, and don't allow increasing call count too much in one iteration. 386 newCallCount = de::clamp(newCallCount, 1, prevMaxCalls*10); 387 } 388 } 389 390 m_measureState.start(m_params.maxCalibrateIterationFrames, m_params.calibrateIterationShortcutThreshold, newCallCount); 391 m_calibrateIterations.push_back(CalibrateIteration(newCallCount, 0.0f)); 392 } 393 } 394 395 void logCalibrationInfo (tcu::TestLog& log, const TheilSenCalibrator& calibrator) 396 { 397 const CalibratorParameters& params = calibrator.getParameters(); 398 const std::vector<CalibrateIteration>& calibrateIterations = calibrator.getCalibrationInfo(); 399 400 // Write out default calibration info. 401 402 log << TestLog::Section("CalibrationInfo", "Calibration Info") 403 << TestLog::Message << "Target frame time: " << params.targetFrameTimeUs << " us (" << 1000000 / params.targetFrameTimeUs << " fps)" << TestLog::EndMessage; 404 405 for (int iterNdx = 0; iterNdx < (int)calibrateIterations.size(); iterNdx++) 406 { 407 log << TestLog::Message << " iteration " << iterNdx << ": " << calibrateIterations[iterNdx].numDrawCalls << " calls => " 408 << de::floatToString(calibrateIterations[iterNdx].frameTime, 2) << " us (" 409 << de::floatToString(1000000.0f / calibrateIterations[iterNdx].frameTime, 2) << " fps)" << TestLog::EndMessage; 410 } 411 log << TestLog::Integer("CallCount", "Calibrated call count", "", QP_KEY_TAG_NONE, calibrator.getMeasureState().numDrawCalls) 412 << TestLog::Integer("FrameCount", "Calibrated frame count", "", QP_KEY_TAG_NONE, (int)calibrator.getMeasureState().frameTimes.size()); 413 log << TestLog::EndSection; 414 } 415 416 } // gls 417 } // deqp 418