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