Import dEQP.
[platform/upstream/VK-GL-CTS.git] / modules / glshared / glsCalibration.cpp
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 "deStringUtil.hpp"
27 #include "deMath.h"
28 #include "deClock.h"
29
30 #include <algorithm>
31 #include <limits>
32
33 using std::string;
34 using std::vector;
35 using tcu::Vec2;
36 using tcu::TestLog;
37 using tcu::TestNode;
38 using namespace glu;
39
40 namespace deqp
41 {
42 namespace gls
43 {
44
45 LineParameters theilSenEstimator (const std::vector<tcu::Vec2>& dataPoints)
46 {
47         const float             epsilon                                 = 1e-6f;
48
49         const int               numDataPoints                   = (int)dataPoints.size();
50         vector<float>   pairwiseCoefficients;
51         vector<float>   pointwiseOffsets;
52         LineParameters  result                                  (0.0f, 0.0f);
53
54         // Compute the pairwise coefficients.
55         for (int i = 0; i < numDataPoints; i++)
56         {
57                 const Vec2& ptA = dataPoints[i];
58
59                 for (int j = 0; j < i; j++)
60                 {
61                         const Vec2& ptB = dataPoints[j];
62
63                         if (de::abs(ptA.x() - ptB.x()) > epsilon)
64                                 pairwiseCoefficients.push_back((ptA.y() - ptB.y()) / (ptA.x() - ptB.x()));
65                 }
66         }
67
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();
72         if (numCoeffs > 0)
73                 result.coefficient = numCoeffs % 2 == 0
74                                                    ? (pairwiseCoefficients[numCoeffs/2 - 1] + pairwiseCoefficients[numCoeffs/2]) / 2
75                                                    : pairwiseCoefficients[numCoeffs/2];
76
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());
80
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();
85         if (numOffsets > 0)
86                 result.offset = numOffsets % 2 == 0
87                                           ? (pointwiseOffsets[numOffsets/2 - 1] + pointwiseOffsets[numOffsets/2]) / 2
88                                           : pointwiseOffsets[numOffsets/2];
89
90         return result;
91 }
92
93 bool MeasureState::isDone (void) const
94 {
95         return (int)frameTimes.size() >= maxNumFrames || (frameTimes.size() >= 2 &&
96                                                                                                           frameTimes[frameTimes.size()-2] >= (deUint64)frameShortcutTime &&
97                                                                                                           frameTimes[frameTimes.size()-1] >= (deUint64)frameShortcutTime);
98 }
99
100 deUint64 MeasureState::getTotalTime (void) const
101 {
102         deUint64 time = 0;
103         for (int i = 0; i < (int)frameTimes.size(); i++)
104                 time += frameTimes[i];
105         return time;
106 }
107
108 void MeasureState::clear (void)
109 {
110         maxNumFrames            = 0;
111         frameShortcutTime       = std::numeric_limits<float>::infinity();
112         numDrawCalls            = 0;
113         frameTimes.clear();
114 }
115
116 void MeasureState::start (int maxNumFrames_, float frameShortcutTime_, int numDrawCalls_)
117 {
118         frameTimes.clear();
119         frameTimes.reserve(maxNumFrames_);
120         maxNumFrames            = maxNumFrames_;
121         frameShortcutTime       = frameShortcutTime_;
122         numDrawCalls            = numDrawCalls_;
123 }
124
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)
129 {
130         clear();
131 }
132
133 TheilSenCalibrator::TheilSenCalibrator (const CalibratorParameters& params)
134         : m_params      (params)
135         , m_state       (INTERNALSTATE_LAST)
136 {
137         clear();
138 }
139
140 TheilSenCalibrator::~TheilSenCalibrator()
141 {
142 }
143
144 void TheilSenCalibrator::clear (void)
145 {
146         m_measureState.clear();
147         m_calibrateIterations.clear();
148         m_state = INTERNALSTATE_CALIBRATING;
149 }
150
151 void TheilSenCalibrator::clear (const CalibratorParameters& params)
152 {
153         m_params = params;
154         clear();
155 }
156
157 TheilSenCalibrator::State TheilSenCalibrator::getState (void) const
158 {
159         if (m_state == INTERNALSTATE_FINISHED)
160                 return STATE_FINISHED;
161         else
162         {
163                 DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING || !m_measureState.isDone());
164                 return m_measureState.isDone() ? STATE_RECOMPUTE_PARAMS : STATE_MEASURE;
165         }
166 }
167
168 void TheilSenCalibrator::recordIteration (deUint64 iterationTime)
169 {
170         DE_ASSERT((m_state == INTERNALSTATE_CALIBRATING || m_state == INTERNALSTATE_RUNNING) && !m_measureState.isDone());
171         m_measureState.frameTimes.push_back(iterationTime);
172
173         if (m_state == INTERNALSTATE_RUNNING && m_measureState.isDone())
174                 m_state = INTERNALSTATE_FINISHED;
175 }
176
177 void TheilSenCalibrator::recomputeParameters (void)
178 {
179         DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING);
180         DE_ASSERT(m_measureState.isDone());
181
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;
185
186         const int               numIterations           = (int)m_calibrateIterations.size();
187
188         // Record frame time.
189         if (numIterations > 0)
190         {
191                 m_calibrateIterations.back().frameTime = (float)((double)m_measureState.getTotalTime() / (double)m_measureState.frameTimes.size());
192
193                 // Check if we're good enough to stop calibrating.
194                 {
195                         bool endCalibration = false;
196
197                         // Is the maximum calibration iteration limit reached?
198                         endCalibration = endCalibration || (int)m_calibrateIterations.size() >= m_params.maxCalibrateIterations;
199
200                         // Do a few past iterations have frame time in acceptable range?
201                         {
202                                 const int numRelevantPastIterations = 2;
203
204                                 if (!endCalibration && (int)m_calibrateIterations.size() >= numRelevantPastIterations)
205                                 {
206                                         const CalibrateIteration* const         past                    = &m_calibrateIterations[m_calibrateIterations.size() - numRelevantPastIterations];
207                                         bool                                                            allInGoodRange  = true;
208
209                                         for (int i = 0; i < numRelevantPastIterations && allInGoodRange; i++)
210                                         {
211                                                 const float frameTimeUs = past[i].frameTime;
212                                                 if (!de::inRange(frameTimeUs, minGoodFrameTimeUs, maxGoodFrameTimeUs))
213                                                         allInGoodRange = false;
214                                         }
215
216                                         endCalibration = endCalibration || allInGoodRange;
217                                 }
218                         }
219
220                         // Do a few past iterations have similar-enough call counts?
221                         {
222                                 const int numRelevantPastIterations = 3;
223                                 if (!endCalibration && (int)m_calibrateIterations.size() >= numRelevantPastIterations)
224                                 {
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();
228
229                                         for (int i = 0; i < numRelevantPastIterations; i++)
230                                         {
231                                                 minCallCount = de::min(minCallCount, past[i].numDrawCalls);
232                                                 maxCallCount = de::max(maxCallCount, past[i].numDrawCalls);
233                                         }
234
235                                         if ((float)(maxCallCount - minCallCount) <= (float)minCallCount * 0.1f)
236                                                 endCalibration = true;
237                                 }
238                         }
239
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);
242
243                         if (endCalibration)
244                         {
245                                 const int       minFrames                       = 10;
246                                 const int       maxFrames                       = 60;
247                                 int                     numMeasureFrames        = deClamp32(deRoundFloatToInt32(m_params.targetMeasureDurationUs / m_calibrateIterations.back().frameTime), minFrames, maxFrames);
248
249                                 m_state = INTERNALSTATE_RUNNING;
250                                 m_measureState.start(numMeasureFrames, m_params.calibrateIterationShortcutThreshold, m_calibrateIterations.back().numDrawCalls);
251                                 return;
252                         }
253                 }
254         }
255
256         DE_ASSERT(m_state == INTERNALSTATE_CALIBRATING);
257
258         // Estimate new call count.
259         {
260                 int newCallCount;
261
262                 if (numIterations == 0)
263                         newCallCount = m_params.numInitialCalls;
264                 else
265                 {
266                         vector<Vec2> dataPoints;
267                         for (int i = 0; i < numIterations; i++)
268                         {
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));
271                         }
272
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.
275
276                         {
277                                 const float                             targetFrameTimeUs       = m_params.targetFrameTimeUs;
278                                 const float                             coeffEpsilon            = 0.001f; // Coefficient must be large enough (and positive) to be considered sensible.
279
280                                 const LineParameters    estimatorLine           = theilSenEstimator(dataPoints);
281
282                                 int                                             prevMaxCalls            = 0;
283
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);
287
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;
290                                 else
291                                 {
292                                         // Solve newCallCount such that approximately targetFrameTime = offset + coefficient*newCallCount.
293                                         newCallCount = (int)((targetFrameTimeUs - estimatorLine.offset) / estimatorLine.coefficient + 0.5f);
294
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)
297                                                 newCallCount++;
298                                 }
299
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);
302                         }
303                 }
304
305                 m_measureState.start(m_params.maxCalibrateIterationFrames, m_params.calibrateIterationShortcutThreshold, newCallCount);
306                 m_calibrateIterations.push_back(CalibrateIteration(newCallCount, 0.0f));
307         }
308 }
309
310 void logCalibrationInfo (tcu::TestLog& log, const TheilSenCalibrator& calibrator)
311 {
312         const CalibratorParameters&                             params                          = calibrator.getParameters();
313         const std::vector<CalibrateIteration>&  calibrateIterations     = calibrator.getCalibrationInfo();
314
315         // Write out default calibration info.
316
317         log << TestLog::Section("CalibrationInfo", "Calibration Info")
318                 << TestLog::Message  << "Target frame time: " << params.targetFrameTimeUs << " us (" << 1000000 / params.targetFrameTimeUs << " fps)" << TestLog::EndMessage;
319
320         for (int iterNdx = 0; iterNdx < (int)calibrateIterations.size(); iterNdx++)
321         {
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;
325         }
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;
329 }
330
331 } // gls
332 } // deqp