es2fFlushFinishTests.cpp revision 3c827367444ee418f129b2c238299f49d3264554
1/*-------------------------------------------------------------------------
2 * drawElements Quality Program OpenGL ES 2.0 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 Flush and finish tests.
22 *//*--------------------------------------------------------------------*/
23
24#include "es2fFlushFinishTests.hpp"
25
26#include "gluRenderContext.hpp"
27#include "gluObjectWrapper.hpp"
28#include "gluShaderProgram.hpp"
29#include "gluDrawUtil.hpp"
30
31#include "glsCalibration.hpp"
32
33#include "tcuTestLog.hpp"
34#include "tcuRenderTarget.hpp"
35
36#include "glwEnums.hpp"
37#include "glwFunctions.hpp"
38
39#include "deRandom.hpp"
40#include "deStringUtil.hpp"
41#include "deClock.h"
42#include "deThread.h"
43#include "deMath.h"
44
45#include <algorithm>
46
47namespace deqp
48{
49namespace gles2
50{
51namespace Functional
52{
53
54using std::vector;
55using std::string;
56using tcu::TestLog;
57using tcu::Vec2;
58using deqp::gls::theilSenEstimator;
59using deqp::gls::LineParameters;
60
61namespace
62{
63
64enum
65{
66	MAX_VIEWPORT_SIZE		= 128,
67	MAX_SAMPLE_DURATION_US	= 1000*1000,
68	WAIT_TIME_MS			= 1200,
69	NUM_SAMPLES				= 25,
70	MIN_DRAW_CALL_COUNT		= 10,
71	MAX_DRAW_CALL_COUNT		= 1<<20,
72	NUM_ITERS_IN_SHADER		= 10
73};
74
75const float		NO_CORR_COEF_THRESHOLD		= 0.1f;
76const float		FLUSH_COEF_THRESHOLD		= 0.2f;
77const float		CORRELATED_COEF_THRESHOLD	= 0.5f;
78
79static void busyWait (int milliseconds)
80{
81	const deUint64	startTime	= deGetMicroseconds();
82	float			v			= 2.0f;
83
84	for (;;)
85	{
86		for (int i = 0; i < 10; i++)
87			v = deFloatSin(v);
88
89		if (deGetMicroseconds()-startTime >= deUint64(1000*milliseconds))
90			break;
91	}
92}
93
94class CalibrationFailedException : public std::runtime_error
95{
96public:
97	CalibrationFailedException (const std::string& reason) : std::runtime_error(reason) {}
98};
99
100class FlushFinishCase : public TestCase
101{
102public:
103	enum ExpectedBehavior
104	{
105		EXPECT_COEF_LESS_THAN = 0,
106		EXPECT_COEF_GREATER_THAN,
107	};
108
109							FlushFinishCase		(Context&			context,
110												 const char*		name,
111												 const char*		description,
112												 ExpectedBehavior	waitBehavior,
113												 float				waitThreshold,
114												 ExpectedBehavior	readBehavior,
115												 float				readThreshold);
116							~FlushFinishCase	(void);
117
118	void					init				(void);
119	void					deinit				(void);
120	IterateResult			iterate				(void);
121
122	struct Sample
123	{
124		int			numDrawCalls;
125		deUint64	waitTime;
126		deUint64	readPixelsTime;
127	};
128
129	struct CalibrationParams
130	{
131		int			maxDrawCalls;
132	};
133
134protected:
135	virtual void			waitForGL			(void) = 0;
136
137private:
138							FlushFinishCase		(const FlushFinishCase&);
139	FlushFinishCase&		operator=			(const FlushFinishCase&);
140
141	CalibrationParams		calibrate			(void);
142	void					analyzeResults		(const std::vector<Sample>& samples, const CalibrationParams& calibrationParams);
143
144	void					setupRenderState	(void);
145	void					render				(int numDrawCalls);
146	void					readPixels			(void);
147
148	const ExpectedBehavior	m_waitBehavior;
149	const float				m_waitThreshold;
150	const ExpectedBehavior	m_readBehavior;
151	const float				m_readThreshold;
152
153	glu::ShaderProgram*		m_program;
154};
155
156FlushFinishCase::FlushFinishCase (Context& context, const char* name, const char* description, ExpectedBehavior waitBehavior, float waitThreshold, ExpectedBehavior readBehavior, float readThreshold)
157	: TestCase			(context, name, description)
158	, m_waitBehavior	(waitBehavior)
159	, m_waitThreshold	(waitThreshold)
160	, m_readBehavior	(readBehavior)
161	, m_readThreshold	(readThreshold)
162	, m_program			(DE_NULL)
163{
164}
165
166FlushFinishCase::~FlushFinishCase (void)
167{
168	FlushFinishCase::deinit();
169}
170
171void FlushFinishCase::init (void)
172{
173	DE_ASSERT(!m_program);
174
175	m_program = new glu::ShaderProgram(m_context.getRenderContext(),
176		glu::ProgramSources()
177			<< glu::VertexSource(
178				"attribute highp vec4 a_position;\n"
179				"varying highp vec4 v_coord;\n"
180				"void main (void)\n"
181				"{\n"
182				"	gl_Position = a_position;\n"
183				"	v_coord = a_position;\n"
184				"}\n")
185			<< glu::FragmentSource(
186				"uniform mediump int u_numIters;\n"
187				"varying mediump vec4 v_coord;\n"
188				"void main (void)\n"
189				"{\n"
190				"	highp vec4 color = v_coord;\n"
191				"	for (int i = 0; i < " + de::toString(int(NUM_ITERS_IN_SHADER)) + "; i++)\n"
192				"		color = sin(color);\n"
193				"	gl_FragColor = color;\n"
194				"}\n"));
195
196	if (!m_program->isOk())
197	{
198		m_testCtx.getLog() << *m_program;
199		delete m_program;
200		m_program = DE_NULL;
201		TCU_FAIL("Compile failed");
202	}
203}
204
205void FlushFinishCase::deinit (void)
206{
207	delete m_program;
208	m_program = DE_NULL;
209}
210
211tcu::TestLog& operator<< (tcu::TestLog& log, const FlushFinishCase::Sample& sample)
212{
213	log << TestLog::Message << sample.numDrawCalls << " calls:\t" << sample.waitTime << " us wait,\t" << sample.readPixelsTime << " us read" << TestLog::EndMessage;
214	return log;
215}
216
217void FlushFinishCase::setupRenderState (void)
218{
219	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
220	const int				posLoc			= gl.getAttribLocation(m_program->getProgram(), "a_position");
221	const int				viewportW		= de::min<int>(m_context.getRenderTarget().getWidth(), MAX_VIEWPORT_SIZE);
222	const int				viewportH		= de::min<int>(m_context.getRenderTarget().getHeight(), MAX_VIEWPORT_SIZE);
223
224	static const float s_positions[] =
225	{
226		-1.0f, -1.0f,
227		+1.0f, -1.0f,
228		-1.0f, +1.0f,
229		+1.0f, +1.0f
230	};
231
232	TCU_CHECK(posLoc >= 0);
233
234	gl.viewport(0, 0, viewportW, viewportH);
235	gl.useProgram(m_program->getProgram());
236	gl.enableVertexAttribArray(posLoc);
237	gl.vertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, &s_positions[0]);
238	gl.enable(GL_BLEND);
239	gl.blendFunc(GL_ONE, GL_ONE);
240	gl.blendEquation(GL_FUNC_ADD);
241	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set up render state");
242}
243
244void FlushFinishCase::render (int numDrawCalls)
245{
246	const glw::Functions&	gl	= m_context.getRenderContext().getFunctions();
247
248	const deUint8 indices[] = { 0, 1, 2, 2, 1, 3 };
249
250	gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
251
252	for (int ndx = 0; ndx < numDrawCalls; ndx++)
253		gl.drawElements(GL_TRIANGLES, DE_LENGTH_OF_ARRAY(indices), GL_UNSIGNED_BYTE, &indices[0]);
254}
255
256void FlushFinishCase::readPixels (void)
257{
258	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
259	deUint8					tmp[4];
260
261	gl.readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &tmp);
262}
263
264FlushFinishCase::CalibrationParams FlushFinishCase::calibrate (void)
265{
266	tcu::ScopedLogSection		section				(m_testCtx.getLog(), "CalibrationInfo", "Calibration info");
267	CalibrationParams			params;
268
269	// Find draw call count that results in desired maximum time.
270	{
271		deUint64			prevDuration			= 0;
272		int					prevDrawCount			= 1;
273		int					curDrawCount			= 1;
274
275		m_testCtx.getLog() << TestLog::Message << "Calibrating maximum draw call count, target duration = " << int(MAX_SAMPLE_DURATION_US) << " us" << TestLog::EndMessage;
276
277		for (;;)
278		{
279			deUint64 curDuration;
280
281			{
282				const deUint64	startTime	= deGetMicroseconds();
283				render(curDrawCount);
284				readPixels();
285				curDuration = deGetMicroseconds()-startTime;
286			}
287
288			m_testCtx.getLog() << TestLog::Message << "Duration with " << curDrawCount << " draw calls = " << curDuration << " us" << TestLog::EndMessage;
289
290			if (curDuration > MAX_SAMPLE_DURATION_US)
291			{
292				if (curDrawCount > 1)
293				{
294					// Compute final count by using linear estimation.
295					const float		a		= float(curDuration - prevDuration) / float(curDrawCount - prevDrawCount);
296					const float		b		= float(prevDuration) - a*float(prevDrawCount);
297					const float		est		= (float(MAX_SAMPLE_DURATION_US) - b) / a;
298
299					curDrawCount = de::clamp(deFloorFloatToInt32(est), 1, int(MAX_DRAW_CALL_COUNT));
300				}
301				// else: Settle on 1.
302
303				break;
304			}
305			else if (curDrawCount >= MAX_DRAW_CALL_COUNT)
306				break; // Settle on maximum.
307			else
308			{
309				prevDrawCount	= curDrawCount;
310				prevDuration	= curDuration;
311				curDrawCount	= curDrawCount*2;
312			}
313		}
314
315		params.maxDrawCalls = curDrawCount;
316
317		m_testCtx.getLog() << TestLog::Integer("MaxDrawCalls", "Maximum number of draw calls", "", QP_KEY_TAG_NONE, params.maxDrawCalls);
318	}
319
320	// Sanity check.
321	if (params.maxDrawCalls < MIN_DRAW_CALL_COUNT)
322		throw CalibrationFailedException("Calibration failed, maximum draw call count is too low");
323
324	return params;
325}
326
327struct CompareSampleDrawCount
328{
329	bool operator() (const FlushFinishCase::Sample& a, const FlushFinishCase::Sample& b) const { return a.numDrawCalls < b.numDrawCalls; }
330};
331
332std::vector<Vec2> getPointsFromSamples (const std::vector<FlushFinishCase::Sample>& samples, const deUint64 FlushFinishCase::Sample::*field)
333{
334	vector<Vec2> points(samples.size());
335
336	for (size_t ndx = 0; ndx < samples.size(); ndx++)
337		points[ndx] = Vec2(float(samples[ndx].numDrawCalls), float(samples[ndx].*field));
338
339	return points;
340}
341
342template<typename T>
343T getMaximumValue (const std::vector<FlushFinishCase::Sample>& samples, const T FlushFinishCase::Sample::*field)
344{
345	DE_ASSERT(!samples.empty());
346
347	T maxVal = samples[0].*field;
348
349	for (size_t ndx = 1; ndx < samples.size(); ndx++)
350		maxVal = de::max(maxVal, samples[ndx].*field);
351
352	return maxVal;
353}
354
355void FlushFinishCase::analyzeResults (const std::vector<Sample>& samples, const CalibrationParams& calibrationParams)
356{
357	const vector<Vec2>		waitTimes		= getPointsFromSamples(samples, &Sample::waitTime);
358	const vector<Vec2>		readTimes		= getPointsFromSamples(samples, &Sample::readPixelsTime);
359	const LineParameters	waitLine		= theilSenEstimator(waitTimes);
360	const LineParameters	readLine		= theilSenEstimator(readTimes);
361	const float				normWaitCoef	= waitLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US);
362	const float				normReadCoef	= readLine.coefficient * float(calibrationParams.maxDrawCalls) / float(MAX_SAMPLE_DURATION_US);
363	bool					allOk			= true;
364
365	{
366		tcu::ScopedLogSection	section			(m_testCtx.getLog(), "Samples", "Samples");
367		vector<Sample>			sortedSamples	(samples.begin(), samples.end());
368
369		std::sort(sortedSamples.begin(), sortedSamples.end(), CompareSampleDrawCount());
370
371		for (vector<Sample>::const_iterator iter = sortedSamples.begin(); iter != sortedSamples.end(); ++iter)
372			m_testCtx.getLog() << *iter;
373	}
374
375	m_testCtx.getLog() << TestLog::Float("WaitCoefficient",				"Wait coefficient", "", QP_KEY_TAG_NONE, waitLine.coefficient)
376					   << TestLog::Float("ReadCoefficient",				"Read coefficient", "", QP_KEY_TAG_NONE, readLine.coefficient)
377					   << TestLog::Float("NormalizedWaitCoefficient",	"Normalized wait coefficient", "", QP_KEY_TAG_NONE, normWaitCoef)
378					   << TestLog::Float("NormalizedReadCoefficient",	"Normalized read coefficient", "", QP_KEY_TAG_NONE, normReadCoef);
379
380	{
381		const bool		waitCorrelated		= normWaitCoef > CORRELATED_COEF_THRESHOLD;
382		const bool		readCorrelated		= normReadCoef > CORRELATED_COEF_THRESHOLD;
383		const bool		waitNotCorr			= normWaitCoef < NO_CORR_COEF_THRESHOLD;
384		const bool		readNotCorr			= normReadCoef < NO_CORR_COEF_THRESHOLD;
385
386		if (waitCorrelated || waitNotCorr)
387			m_testCtx.getLog() << TestLog::Message << "Wait time is" << (waitCorrelated ? "" : " NOT") << " correlated to rendering workload size." << TestLog::EndMessage;
388		else
389			m_testCtx.getLog() << TestLog::Message << "Warning: Wait time correlation to rendering workload size is unclear." << TestLog::EndMessage;
390
391		if (readCorrelated || readNotCorr)
392			m_testCtx.getLog() << TestLog::Message << "Read time is" << (readCorrelated ? "" : " NOT") << " correlated to rendering workload size." << TestLog::EndMessage;
393		else
394			m_testCtx.getLog() << TestLog::Message << "Warning: Read time correlation to rendering workload size is unclear." << TestLog::EndMessage;
395	}
396
397	for (int ndx = 0; ndx < 2; ndx++)
398	{
399		const float				coef		= ndx == 0 ? normWaitCoef : normReadCoef;
400		const char*				name		= ndx == 0 ? "wait" : "read";
401		const ExpectedBehavior	behavior	= ndx == 0 ? m_waitBehavior : m_readBehavior;
402		const float				threshold	= ndx == 0 ? m_waitThreshold : m_readThreshold;
403		const bool				isOk		= behavior == EXPECT_COEF_GREATER_THAN	? coef > threshold :
404											  behavior == EXPECT_COEF_LESS_THAN		? coef < threshold : false;
405		const char*				cmpName		= behavior == EXPECT_COEF_GREATER_THAN	? "greater than" :
406											  behavior == EXPECT_COEF_LESS_THAN		? "less than" : DE_NULL;
407
408		if (!isOk)
409		{
410			m_testCtx.getLog() << TestLog::Message << "ERROR: Expected " << name << " coefficient to be " << cmpName << " " << threshold << TestLog::EndMessage;
411			allOk = false;
412		}
413	}
414
415	m_testCtx.setTestResult(allOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
416							allOk ? "Pass"				: "Suspicious performance behavior");
417}
418
419FlushFinishCase::IterateResult FlushFinishCase::iterate (void)
420{
421	vector<Sample>		samples		(NUM_SAMPLES);
422	CalibrationParams	params;
423
424	// Try to poke CPU into full speed. \todo [2013-12-26 pyry] Use more robust method.
425	busyWait(10);
426
427	setupRenderState();
428
429	// Do one full render cycle.
430	{
431		render(1);
432		readPixels();
433	}
434
435	// Calibrate.
436	try
437	{
438		params = calibrate();
439	}
440	catch (const CalibrationFailedException& e)
441	{
442		m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, e.what());
443		return STOP;
444	}
445
446	// Do measurement.
447	{
448		de::Random	rnd		(123);
449
450		for (size_t ndx = 0; ndx < samples.size(); ndx++)
451		{
452			const int	drawCallCount	= rnd.getInt(1, params.maxDrawCalls);
453			deUint64	waitStartTime;
454			deUint64	readStartTime;
455			deUint64	readFinishTime;
456
457			render(drawCallCount);
458
459			waitStartTime = deGetMicroseconds();
460			waitForGL();
461
462			readStartTime = deGetMicroseconds();
463			readPixels();
464			readFinishTime = deGetMicroseconds();
465
466			samples[ndx].numDrawCalls	= drawCallCount;
467			samples[ndx].waitTime		= readStartTime-waitStartTime;
468			samples[ndx].readPixelsTime	= readFinishTime-readStartTime;
469
470			if (m_testCtx.getWatchDog())
471				qpWatchDog_touch(m_testCtx.getWatchDog());
472		}
473	}
474
475	// Analyze - sets test case result.
476	analyzeResults(samples, params);
477
478	return STOP;
479}
480
481class WaitOnlyCase : public FlushFinishCase
482{
483public:
484	WaitOnlyCase (Context& context)
485		: FlushFinishCase(context, "wait", "Wait only", EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD, EXPECT_COEF_GREATER_THAN, -1000.0f /* practically nothing is expected */)
486	{
487	}
488
489	void init (void)
490	{
491		m_testCtx.getLog() << TestLog::Message << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
492		FlushFinishCase::init();
493	}
494
495protected:
496	void waitForGL (void)
497	{
498		busyWait(WAIT_TIME_MS);
499	}
500};
501
502class FlushOnlyCase : public FlushFinishCase
503{
504public:
505	FlushOnlyCase (Context& context)
506		: FlushFinishCase(context, "flush", "Flush only", EXPECT_COEF_LESS_THAN, FLUSH_COEF_THRESHOLD, EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD)
507	{
508	}
509
510	void init (void)
511	{
512		m_testCtx.getLog() << TestLog::Message << "Single call to glFlush()" << TestLog::EndMessage;
513		FlushFinishCase::init();
514	}
515
516protected:
517	void waitForGL (void)
518	{
519		m_context.getRenderContext().getFunctions().flush();
520	}
521};
522
523class FlushWaitCase : public FlushFinishCase
524{
525public:
526	FlushWaitCase (Context& context)
527		: FlushFinishCase(context, "flush_wait", "Wait after flushing", EXPECT_COEF_LESS_THAN, FLUSH_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
528	{
529	}
530
531	void init (void)
532	{
533		m_testCtx.getLog() << TestLog::Message << "glFlush() followed by " << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
534		FlushFinishCase::init();
535	}
536
537protected:
538	void waitForGL (void)
539	{
540		m_context.getRenderContext().getFunctions().flush();
541		busyWait(WAIT_TIME_MS);
542	}
543};
544
545class FinishOnlyCase : public FlushFinishCase
546{
547public:
548	FinishOnlyCase (Context& context)
549		: FlushFinishCase(context, "finish", "Finish only", EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
550	{
551	}
552
553	void init (void)
554	{
555		m_testCtx.getLog() << TestLog::Message << "Single call to glFinish()" << TestLog::EndMessage;
556		FlushFinishCase::init();
557	}
558
559protected:
560	void waitForGL (void)
561	{
562		m_context.getRenderContext().getFunctions().finish();
563	}
564};
565
566class FinishWaitCase : public FlushFinishCase
567{
568public:
569	FinishWaitCase (Context& context)
570		: FlushFinishCase(context, "finish_wait", "Finish and wait", EXPECT_COEF_GREATER_THAN, CORRELATED_COEF_THRESHOLD, EXPECT_COEF_LESS_THAN, NO_CORR_COEF_THRESHOLD)
571	{
572	}
573
574	void init (void)
575	{
576		m_testCtx.getLog() << TestLog::Message << "glFinish() followed by " << int(WAIT_TIME_MS) << " ms busy wait" << TestLog::EndMessage;
577		FlushFinishCase::init();
578	}
579
580protected:
581	void waitForGL (void)
582	{
583		m_context.getRenderContext().getFunctions().finish();
584		busyWait(WAIT_TIME_MS);
585	}
586};
587
588} // anonymous
589
590FlushFinishTests::FlushFinishTests (Context& context)
591	: TestCaseGroup(context, "flush_finish", "Flush and Finish tests")
592{
593}
594
595FlushFinishTests::~FlushFinishTests (void)
596{
597}
598
599void FlushFinishTests::init (void)
600{
601	addChild(new WaitOnlyCase	(m_context));
602	addChild(new FlushOnlyCase	(m_context));
603	addChild(new FlushWaitCase	(m_context));
604	addChild(new FinishOnlyCase	(m_context));
605	addChild(new FinishWaitCase	(m_context));
606}
607
608} // Functional
609} // gles2
610} // deqp
611