Home | History | Annotate | Download | only in glshared
      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