1 /*-------------------------------------------------------------------------
2 * drawElements Quality Program OpenGL (ES) Module
3 * -----------------------------------------------
5 * Copyright 2014 The Android Open Source Project
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 * \brief Calibration tools.
22 *//*--------------------------------------------------------------------*/
24 #include "glsCalibration.hpp"
25 #include "tcuTestLog.hpp"
26 #include "deStringUtil.hpp"
45 LineParameters theilSenEstimator (const std::vector<tcu::Vec2>& dataPoints)
47 const float epsilon = 1e-6f;
49 const int numDataPoints = (int)dataPoints.size();
50 vector<float> pairwiseCoefficients;
51 vector<float> pointwiseOffsets;
52 LineParameters result (0.0f, 0.0f);
54 // Compute the pairwise coefficients.
55 for (int i = 0; i < numDataPoints; i++)
57 const Vec2& ptA = dataPoints[i];
59 for (int j = 0; j < i; j++)
61 const Vec2& ptB = dataPoints[j];
63 if (de::abs(ptA.x() - ptB.x()) > epsilon)
64 pairwiseCoefficients.push_back((ptA.y() - ptB.y()) / (ptA.x() - ptB.x()));
68 // Find the median of the pairwise coefficients.
69 // \note If there are no data point pairs with differing x values, the coefficient variable will stay zero as initialized.
70 std::sort(pairwiseCoefficients.begin(), pairwiseCoefficients.end());
71 int numCoeffs = (int)pairwiseCoefficients.size();
73 result.coefficient = numCoeffs % 2 == 0
74 ? (pairwiseCoefficients[numCoeffs/2 - 1] + pairwiseCoefficients[numCoeffs/2]) / 2
75 : pairwiseCoefficients[numCoeffs/2];
77 // Compute the offsets corresponding to the median coefficient, for all data points.
78 for (int i = 0; i < numDataPoints; i++)
79 pointwiseOffsets.push_back(dataPoints[i].y() - result.coefficient*dataPoints[i].x());
81 // Find the median of the offsets.
82 // \note If there are no data points, the offset variable will stay zero as initialized.
83 std::sort(pointwiseOffsets.begin(), pointwiseOffsets.end());
84 int numOffsets = (int)pointwiseOffsets.size();
86 result.offset = numOffsets % 2 == 0
87 ? (pointwiseOffsets[numOffsets/2 - 1] + pointwiseOffsets[numOffsets/2]) / 2
88 : pointwiseOffsets[numOffsets/2];
93 bool MeasureState::isDone (void) const
95 return (int)frameTimes.size() >= maxNumFrames || (frameTimes.size() >= 2 &&
96 frameTimes[frameTimes.size()-2] >= (deUint64)frameShortcutTime &&
97 frameTimes[frameTimes.size()-1] >= (deUint64)frameShortcutTime);
100 deUint64 MeasureState::getTotalTime (void) const
103 for (int i = 0; i < (int)frameTimes.size(); i++)
104 time += frameTimes[i];
108 void MeasureState::clear (void)
111 frameShortcutTime = std::numeric_limits<float>::infinity();
116 void MeasureState::start (int maxNumFrames_, float frameShortcutTime_, int numDrawCalls_)
119 frameTimes.reserve(maxNumFrames_);
120 maxNumFrames = maxNumFrames_;
121 frameShortcutTime = frameShortcutTime_;
122 numDrawCalls = numDrawCalls_;
125 TheilSenCalibrator::TheilSenCalibrator (void)
126 : m_params (1 /* initial calls */, 10 /* calibrate iter frames */, 2000.0f /* calibrate iter shortcut threshold */, 31 /* max calibration iterations */,
127 1000.0f/30.0f /* target frame time */, 1000.0f/60.0f /* frame time cap */, 1000.0f /* target measure duration */)
128 , m_state (INTERNALSTATE_LAST)
133 TheilSenCalibrator::TheilSenCalibrator (const CalibratorParameters& params)
135 , m_state (INTERNALSTATE_LAST)
140 TheilSenCalibrator::~TheilSenCalibrator()
144 void TheilSenCalibrator::clear (void)
146 m_measureState.clear();
147 m_calibrateIterations.clear();
148 m_state = INTERNALSTATE_CALIBRATING;
151 void TheilSenCalibrator::clear (const CalibratorParameters& params)
157 TheilSenCalibrator::State TheilSenCalibrator::getState (void) const
159 if (m_state == INTERNALSTATE_FINISHED)
160 return STATE_FINISHED;
163 DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING || !m_measureState.isDone());
164 return m_measureState.isDone() ? STATE_RECOMPUTE_PARAMS : STATE_MEASURE;
168 void TheilSenCalibrator::recordIteration (deUint64 iterationTime)
170 DE_ASSERT((m_state == INTERNALSTATE_CALIBRATING || m_state == INTERNALSTATE_RUNNING) && !m_measureState.isDone());
171 m_measureState.frameTimes.push_back(iterationTime);
173 if (m_state == INTERNALSTATE_RUNNING && m_measureState.isDone())
174 m_state = INTERNALSTATE_FINISHED;
177 void TheilSenCalibrator::recomputeParameters (void)
179 DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING);
180 DE_ASSERT(m_measureState.isDone());
182 // Minimum and maximum acceptable frame times.
183 const float minGoodFrameTimeUs = m_params.targetFrameTimeUs * 0.95f;
184 const float maxGoodFrameTimeUs = m_params.targetFrameTimeUs * 1.15f;
186 const int numIterations = (int)m_calibrateIterations.size();
188 // Record frame time.
189 if (numIterations > 0)
191 m_calibrateIterations.back().frameTime = (float)((double)m_measureState.getTotalTime() / (double)m_measureState.frameTimes.size());
193 // Check if we're good enough to stop calibrating.
195 bool endCalibration = false;
197 // Is the maximum calibration iteration limit reached?
198 endCalibration = endCalibration || (int)m_calibrateIterations.size() >= m_params.maxCalibrateIterations;
200 // Do a few past iterations have frame time in acceptable range?
202 const int numRelevantPastIterations = 2;
204 if (!endCalibration && (int)m_calibrateIterations.size() >= numRelevantPastIterations)
206 const CalibrateIteration* const past = &m_calibrateIterations[m_calibrateIterations.size() - numRelevantPastIterations];
207 bool allInGoodRange = true;
209 for (int i = 0; i < numRelevantPastIterations && allInGoodRange; i++)
211 const float frameTimeUs = past[i].frameTime;
212 if (!de::inRange(frameTimeUs, minGoodFrameTimeUs, maxGoodFrameTimeUs))
213 allInGoodRange = false;
216 endCalibration = endCalibration || allInGoodRange;
220 // Do a few past iterations have similar-enough call counts?
222 const int numRelevantPastIterations = 3;
223 if (!endCalibration && (int)m_calibrateIterations.size() >= numRelevantPastIterations)
225 const CalibrateIteration* const past = &m_calibrateIterations[m_calibrateIterations.size() - numRelevantPastIterations];
226 int minCallCount = std::numeric_limits<int>::max();
227 int maxCallCount = std::numeric_limits<int>::min();
229 for (int i = 0; i < numRelevantPastIterations; i++)
231 minCallCount = de::min(minCallCount, past[i].numDrawCalls);
232 maxCallCount = de::max(maxCallCount, past[i].numDrawCalls);
235 if ((float)(maxCallCount - minCallCount) <= (float)minCallCount * 0.1f)
236 endCalibration = true;
240 // Is call count just 1, and frame time still way too high?
241 endCalibration = endCalibration || (m_calibrateIterations.back().numDrawCalls == 1 && m_calibrateIterations.back().frameTime > m_params.targetFrameTimeUs*2.0f);
245 const int minFrames = 10;
246 const int maxFrames = 60;
247 int numMeasureFrames = deClamp32(deRoundFloatToInt32(m_params.targetMeasureDurationUs / m_calibrateIterations.back().frameTime), minFrames, maxFrames);
249 m_state = INTERNALSTATE_RUNNING;
250 m_measureState.start(numMeasureFrames, m_params.calibrateIterationShortcutThreshold, m_calibrateIterations.back().numDrawCalls);
256 DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING);
258 // Estimate new call count.
262 if (numIterations == 0)
263 newCallCount = m_params.numInitialCalls;
266 vector<Vec2> dataPoints;
267 for (int i = 0; i < numIterations; i++)
269 if (m_calibrateIterations[i].numDrawCalls == 1 || m_calibrateIterations[i].frameTime > m_params.frameTimeCapUs*1.05f) // Only account for measurements not too near the cap.
270 dataPoints.push_back(Vec2((float)m_calibrateIterations[i].numDrawCalls, m_calibrateIterations[i].frameTime));
273 if (numIterations == 1)
274 dataPoints.push_back(Vec2(0.0f, 0.0f)); // If there's just one measurement so far, this will help in getting the next estimate.
277 const float targetFrameTimeUs = m_params.targetFrameTimeUs;
278 const float coeffEpsilon = 0.001f; // Coefficient must be large enough (and positive) to be considered sensible.
280 const LineParameters estimatorLine = theilSenEstimator(dataPoints);
282 int prevMaxCalls = 0;
284 // Find the maximum of the past call counts.
285 for (int i = 0; i < numIterations; i++)
286 prevMaxCalls = de::max(prevMaxCalls, m_calibrateIterations[i].numDrawCalls);
288 if (estimatorLine.coefficient < coeffEpsilon) // Coefficient not good for sensible estimation; increase call count enough to get a reasonably different value.
289 newCallCount = 2*prevMaxCalls;
292 // Solve newCallCount such that approximately targetFrameTime = offset + coefficient*newCallCount.
293 newCallCount = (int)((targetFrameTimeUs - estimatorLine.offset) / estimatorLine.coefficient + 0.5f);
295 // We should generally prefer FPS counts below the target rather than above (i.e. higher frame times rather than lower).
296 if (estimatorLine.offset + estimatorLine.coefficient*(float)newCallCount < minGoodFrameTimeUs)
300 // Make sure we have at least minimum amount of calls, and don't allow increasing call count too much in one iteration.
301 newCallCount = de::clamp(newCallCount, 1, prevMaxCalls*10);
305 m_measureState.start(m_params.maxCalibrateIterationFrames, m_params.calibrateIterationShortcutThreshold, newCallCount);
306 m_calibrateIterations.push_back(CalibrateIteration(newCallCount, 0.0f));
310 void logCalibrationInfo (tcu::TestLog& log, const TheilSenCalibrator& calibrator)
312 const CalibratorParameters& params = calibrator.getParameters();
313 const std::vector<CalibrateIteration>& calibrateIterations = calibrator.getCalibrationInfo();
315 // Write out default calibration info.
317 log << TestLog::Section("CalibrationInfo", "Calibration Info")
318 << TestLog::Message << "Target frame time: " << params.targetFrameTimeUs << " us (" << 1000000 / params.targetFrameTimeUs << " fps)" << TestLog::EndMessage;
320 for (int iterNdx = 0; iterNdx < (int)calibrateIterations.size(); iterNdx++)
322 log << TestLog::Message << " iteration " << iterNdx << ": " << calibrateIterations[iterNdx].numDrawCalls << " calls => "
323 << de::floatToString(calibrateIterations[iterNdx].frameTime, 2) << " us ("
324 << de::floatToString(1000000.0f / calibrateIterations[iterNdx].frameTime, 2) << " fps)" << TestLog::EndMessage;
326 log << TestLog::Integer("CallCount", "Calibrated call count", "", QP_KEY_TAG_NONE, calibrator.getMeasureState().numDrawCalls)
327 << TestLog::Integer("FrameCount", "Calibrated frame count", "", QP_KEY_TAG_NONE, (int)calibrator.getMeasureState().frameTimes.size());
328 log << TestLog::EndSection;