1/*-------------------------------------------------------------------------
2 * drawElements Quality Program OpenGL ES 3.1 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 gl_HelperInvocation tests.
22 *//*--------------------------------------------------------------------*/
23
24#include "es31fShaderHelperInvocationTests.hpp"
25
26#include "gluObjectWrapper.hpp"
27#include "gluShaderProgram.hpp"
28#include "gluDrawUtil.hpp"
29#include "gluPixelTransfer.hpp"
30
31#include "glwFunctions.hpp"
32#include "glwEnums.hpp"
33
34#include "tcuTestLog.hpp"
35#include "tcuVector.hpp"
36#include "tcuSurface.hpp"
37
38#include "deUniquePtr.hpp"
39#include "deStringUtil.hpp"
40#include "deRandom.hpp"
41#include "deString.h"
42
43namespace deqp
44{
45namespace gles31
46{
47namespace Functional
48{
49namespace
50{
51
52using glu::ShaderProgram;
53using tcu::TestLog;
54using tcu::Vec2;
55using tcu::IVec2;
56using de::MovePtr;
57using std::string;
58using std::vector;
59
60enum PrimitiveType
61{
62	PRIMITIVETYPE_TRIANGLE = 0,
63	PRIMITIVETYPE_LINE,
64	PRIMITIVETYPE_WIDE_LINE,
65	PRIMITIVETYPE_POINT,
66	PRIMITIVETYPE_WIDE_POINT,
67
68	PRIMITIVETYPE_LAST
69};
70
71static int getNumVerticesPerPrimitive (PrimitiveType primType)
72{
73	switch (primType)
74	{
75		case PRIMITIVETYPE_TRIANGLE:	return 3;
76		case PRIMITIVETYPE_LINE:		return 2;
77		case PRIMITIVETYPE_WIDE_LINE:	return 2;
78		case PRIMITIVETYPE_POINT:		return 1;
79		case PRIMITIVETYPE_WIDE_POINT:	return 1;
80		default:
81			DE_ASSERT(false);
82			return 0;
83	}
84}
85
86static glu::PrimitiveType getGluPrimitiveType (PrimitiveType primType)
87{
88	switch (primType)
89	{
90		case PRIMITIVETYPE_TRIANGLE:	return glu::PRIMITIVETYPE_TRIANGLES;
91		case PRIMITIVETYPE_LINE:		return glu::PRIMITIVETYPE_LINES;
92		case PRIMITIVETYPE_WIDE_LINE:	return glu::PRIMITIVETYPE_LINES;
93		case PRIMITIVETYPE_POINT:		return glu::PRIMITIVETYPE_POINTS;
94		case PRIMITIVETYPE_WIDE_POINT:	return glu::PRIMITIVETYPE_POINTS;
95		default:
96			DE_ASSERT(false);
97			return glu::PRIMITIVETYPE_LAST;
98	}
99}
100
101static void genVertices (PrimitiveType primType, int numPrimitives, de::Random* rnd, vector<Vec2>* dst)
102{
103	const bool	isTri					= primType == PRIMITIVETYPE_TRIANGLE;
104	const float	minCoord				= isTri ? -1.5f : -1.0f;
105	const float	maxCoord				= isTri ? +1.5f : +1.0f;
106	const int	numVerticesPerPrimitive	= getNumVerticesPerPrimitive(primType);
107	const int	numVert					= numVerticesPerPrimitive*numPrimitives;
108
109	dst->resize(numVert);
110
111	for (size_t ndx = 0; ndx < dst->size(); ndx++)
112	{
113		(*dst)[ndx][0] = rnd->getFloat(minCoord, maxCoord);
114		(*dst)[ndx][1] = rnd->getFloat(minCoord, maxCoord);
115	}
116
117	// Don't produce completely or almost completely discardable primitives.
118	// \note: This doesn't guarantee that resulting primitives are visible or
119	//        produce any fragments. This just removes trivially discardable
120	//        primitives.
121	for (int primitiveNdx = 0; primitiveNdx < numPrimitives; ++primitiveNdx)
122	for (int component = 0; component < 2; ++component)
123	{
124		bool negativeClip = true;
125		bool positiveClip = true;
126
127		for (int vertexNdx = 0; vertexNdx < numVerticesPerPrimitive; ++vertexNdx)
128		{
129			const float p = (*dst)[primitiveNdx * numVerticesPerPrimitive + vertexNdx][component];
130			// \note 0.9 instead of 1.0 to avoid just barely visible primitives
131			if (p > -0.9f)
132				negativeClip = false;
133			if (p < +0.9f)
134				positiveClip = false;
135		}
136
137		// if discardable, just mirror first vertex along center
138		if (negativeClip || positiveClip)
139		{
140			(*dst)[primitiveNdx * numVerticesPerPrimitive + 0][0] *= -1.0f;
141			(*dst)[primitiveNdx * numVerticesPerPrimitive + 0][1] *= -1.0f;
142		}
143	}
144}
145
146static int getInteger (const glw::Functions& gl, deUint32 pname)
147{
148	int v = 0;
149	gl.getIntegerv(pname, &v);
150	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetIntegerv()");
151	return v;
152}
153
154static Vec2 getRange (const glw::Functions& gl, deUint32 pname)
155{
156	Vec2 v(0.0f);
157	gl.getFloatv(pname, v.getPtr());
158	GLU_EXPECT_NO_ERROR(gl.getError(), "glGetFloatv()");
159	return v;
160}
161
162static void drawRandomPrimitives (const glu::RenderContext& renderCtx, deUint32 program, PrimitiveType primType, int numPrimitives, de::Random* rnd)
163{
164	const glw::Functions&			gl				= renderCtx.getFunctions();
165	const float						minPointSize	= 16.0f;
166	const float						maxPointSize	= 32.0f;
167	const float						minLineWidth	= 16.0f;
168	const float						maxLineWidth	= 32.0f;
169	vector<Vec2>					vertices;
170	vector<glu::VertexArrayBinding>	vertexArrays;
171
172	genVertices(primType, numPrimitives, rnd, &vertices);
173
174	vertexArrays.push_back(glu::va::Float("a_position", 2, (int)vertices.size(), 0, (const float*)&vertices[0]));
175
176	gl.useProgram(program);
177
178	// Special state for certain primitives
179	if (primType == PRIMITIVETYPE_POINT || primType == PRIMITIVETYPE_WIDE_POINT)
180	{
181		const Vec2		range			= getRange(gl, GL_ALIASED_POINT_SIZE_RANGE);
182		const bool		isWidePoint		= primType == PRIMITIVETYPE_WIDE_POINT;
183		const float		pointSize		= isWidePoint ? de::min(rnd->getFloat(minPointSize, maxPointSize), range.y()) : 1.0f;
184		const int		pointSizeLoc	= gl.getUniformLocation(program, "u_pointSize");
185
186		gl.uniform1f(pointSizeLoc, pointSize);
187	}
188	else if (primType == PRIMITIVETYPE_WIDE_LINE)
189	{
190		const Vec2		range			= getRange(gl, GL_ALIASED_LINE_WIDTH_RANGE);
191		const float		lineWidth		= de::min(rnd->getFloat(minLineWidth, maxLineWidth), range.y());
192
193		gl.lineWidth(lineWidth);
194	}
195
196	glu::draw(renderCtx, program, (int)vertexArrays.size(), &vertexArrays[0],
197			  glu::PrimitiveList(getGluPrimitiveType(primType), (int)vertices.size()));
198}
199
200class FboHelper
201{
202public:
203								FboHelper			(const glu::RenderContext& renderCtx, int width, int height, deUint32 format, int numSamples);
204								~FboHelper			(void);
205
206	void						bindForRendering	(void);
207	void						readPixels			(int x, int y, const tcu::PixelBufferAccess& dst);
208
209private:
210	const glu::RenderContext&	m_renderCtx;
211	const int					m_numSamples;
212	const IVec2					m_size;
213
214	glu::Renderbuffer			m_colorbuffer;
215	glu::Framebuffer			m_framebuffer;
216	glu::Renderbuffer			m_resolveColorbuffer;
217	glu::Framebuffer			m_resolveFramebuffer;
218};
219
220FboHelper::FboHelper (const glu::RenderContext& renderCtx, int width, int height, deUint32 format, int numSamples)
221	: m_renderCtx			(renderCtx)
222	, m_numSamples			(numSamples)
223	, m_size				(width, height)
224	, m_colorbuffer			(renderCtx)
225	, m_framebuffer			(renderCtx)
226	, m_resolveColorbuffer	(renderCtx)
227	, m_resolveFramebuffer	(renderCtx)
228{
229	const glw::Functions&	gl			= m_renderCtx.getFunctions();
230	const int				maxSamples	= getInteger(gl, GL_MAX_SAMPLES);
231
232	gl.bindRenderbuffer(GL_RENDERBUFFER, *m_colorbuffer);
233	gl.renderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples, format, width, height);
234	gl.bindFramebuffer(GL_FRAMEBUFFER, *m_framebuffer);
235	gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *m_colorbuffer);
236
237	if (m_numSamples > maxSamples && gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
238		throw tcu::NotSupportedError("Sample count exceeds GL_MAX_SAMPLES");
239
240	TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
241
242	if (m_numSamples != 0)
243	{
244		gl.bindRenderbuffer(GL_RENDERBUFFER, *m_resolveColorbuffer);
245		gl.renderbufferStorage(GL_RENDERBUFFER, format, width, height);
246		gl.bindFramebuffer(GL_FRAMEBUFFER, *m_resolveFramebuffer);
247		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *m_resolveColorbuffer);
248		TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
249	}
250
251	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create framebuffer");
252}
253
254FboHelper::~FboHelper (void)
255{
256}
257
258void FboHelper::bindForRendering (void)
259{
260	const glw::Functions& gl = m_renderCtx.getFunctions();
261	gl.bindFramebuffer(GL_FRAMEBUFFER, *m_framebuffer);
262	GLU_EXPECT_NO_ERROR(gl.getError(), "glBindFramebuffer()");
263	gl.viewport(0, 0, m_size.x(), m_size.y());
264	GLU_EXPECT_NO_ERROR(gl.getError(), "viewport()");
265}
266
267void FboHelper::readPixels (int x, int y, const tcu::PixelBufferAccess& dst)
268{
269	const glw::Functions&	gl		= m_renderCtx.getFunctions();
270	const int				width	= dst.getWidth();
271	const int				height	= dst.getHeight();
272
273	if (m_numSamples != 0)
274	{
275		gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, *m_resolveFramebuffer);
276		gl.blitFramebuffer(x, y, width, height, x, y, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
277		gl.bindFramebuffer(GL_READ_FRAMEBUFFER, *m_resolveFramebuffer);
278	}
279
280	glu::readPixels(m_renderCtx, x, y, dst);
281}
282
283enum
284{
285	FRAMEBUFFER_WIDTH	= 256,
286	FRAMEBUFFER_HEIGHT	= 256,
287	FRAMEBUFFER_FORMAT	= GL_RGBA8,
288	NUM_SAMPLES_MAX		= -1
289};
290
291//! Verifies that gl_HelperInvocation is false in all rendered pixels.
292class HelperInvocationValueCase : public TestCase
293{
294public:
295							HelperInvocationValueCase	(Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples);
296							~HelperInvocationValueCase	(void);
297
298	void					init						(void);
299	void					deinit						(void);
300	IterateResult			iterate						(void);
301
302private:
303	const PrimitiveType		m_primitiveType;
304	const int				m_numSamples;
305
306	const int				m_numIters;
307	const int				m_numPrimitivesPerIter;
308
309	MovePtr<ShaderProgram>	m_program;
310	MovePtr<FboHelper>		m_fbo;
311	int						m_iterNdx;
312};
313
314HelperInvocationValueCase::HelperInvocationValueCase (Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples)
315	: TestCase					(context, name, description)
316	, m_primitiveType			(primType)
317	, m_numSamples				(numSamples)
318	, m_numIters				(5)
319	, m_numPrimitivesPerIter	(10)
320	, m_iterNdx					(0)
321{
322}
323
324HelperInvocationValueCase::~HelperInvocationValueCase (void)
325{
326	deinit();
327}
328
329void HelperInvocationValueCase::init (void)
330{
331	const glu::RenderContext&	renderCtx		= m_context.getRenderContext();
332	const glw::Functions&		gl				= renderCtx.getFunctions();
333	const int					maxSamples		= getInteger(gl, GL_MAX_SAMPLES);
334	const int					actualSamples	= m_numSamples == NUM_SAMPLES_MAX ? maxSamples : m_numSamples;
335
336	m_program = MovePtr<ShaderProgram>(new ShaderProgram(m_context.getRenderContext(),
337		glu::ProgramSources()
338			<< glu::VertexSource(
339				"#version 310 es\n"
340				"in highp vec2 a_position;\n"
341				"uniform highp float u_pointSize;\n"
342				"void main (void)\n"
343				"{\n"
344				"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
345				"	gl_PointSize = u_pointSize;\n"
346				"}\n")
347			<< glu::FragmentSource(
348				"#version 310 es\n"
349				"out mediump vec4 o_color;\n"
350				"void main (void)\n"
351				"{\n"
352				"	if (gl_HelperInvocation)\n"
353				"		o_color = vec4(1.0, 0.0, 0.0, 1.0);\n"
354				"	else\n"
355				"		o_color = vec4(0.0, 1.0, 0.0, 1.0);\n"
356				"}\n")));
357
358	m_testCtx.getLog() << *m_program;
359
360	if (!m_program->isOk())
361	{
362		m_program.clear();
363		TCU_FAIL("Compile failed");
364	}
365
366	m_testCtx.getLog() << TestLog::Message << "Using GL_RGBA8 framebuffer with "
367					   << actualSamples << " samples" << TestLog::EndMessage;
368
369	m_fbo = MovePtr<FboHelper>(new FboHelper(renderCtx, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT,
370											 FRAMEBUFFER_FORMAT, actualSamples));
371
372	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
373}
374
375void HelperInvocationValueCase::deinit (void)
376{
377	m_program.clear();
378	m_fbo.clear();
379}
380
381static bool verifyHelperInvocationValue (TestLog& log, const tcu::Surface& result, bool isMultiSample)
382{
383	const tcu::RGBA		bgRef				(0, 0, 0, 255);
384	const tcu::RGBA		fgRef				(0, 255, 0, 255);
385	const tcu::RGBA		threshold			(1, isMultiSample ? 254 : 1, 1, 1);
386	int					numInvalidPixels	= 0;
387	bool				renderedSomething	= false;
388
389	for (int y = 0; y < result.getHeight(); ++y)
390	{
391		for (int x = 0; x < result.getWidth(); ++x)
392		{
393			const tcu::RGBA	resPix	= result.getPixel(x, y);
394			const bool		isBg	= tcu::compareThreshold(resPix, bgRef, threshold);
395			const bool		isFg	= tcu::compareThreshold(resPix, fgRef, threshold);
396
397			if (!isBg && !isFg)
398				numInvalidPixels += 1;
399
400			if (isFg)
401				renderedSomething = true;
402		}
403	}
404
405	if (numInvalidPixels > 0)
406	{
407		log << TestLog::Image("Result", "Result image", result);
408		log << TestLog::Message << "ERROR: Found " << numInvalidPixels << " invalid result pixels!" << TestLog::EndMessage;
409		return false;
410	}
411	else if (!renderedSomething)
412	{
413		log << TestLog::Image("Result", "Result image", result);
414		log << TestLog::Message << "ERROR: Result image was empty!" << TestLog::EndMessage;
415		return false;
416	}
417	else
418	{
419		log << TestLog::Message << "All result pixels are valid" << TestLog::EndMessage;
420		return true;
421	}
422}
423
424HelperInvocationValueCase::IterateResult HelperInvocationValueCase::iterate (void)
425{
426	const glu::RenderContext&		renderCtx	= m_context.getRenderContext();
427	const glw::Functions&			gl			= renderCtx.getFunctions();
428	const string					sectionName	= string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(m_numIters);
429	const tcu::ScopedLogSection		section		(m_testCtx.getLog(), (string("Iter") + de::toString(m_iterNdx)), sectionName);
430	de::Random						rnd			(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
431	tcu::Surface					result		(FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);
432
433	m_fbo->bindForRendering();
434	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
435	gl.clear(GL_COLOR_BUFFER_BIT);
436
437	drawRandomPrimitives(renderCtx, m_program->getProgram(), m_primitiveType, m_numPrimitivesPerIter, &rnd);
438
439	m_fbo->readPixels(0, 0, result.getAccess());
440
441	if (!verifyHelperInvocationValue(m_testCtx.getLog(), result, m_numSamples != 0))
442		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid pixels found");
443
444	m_iterNdx += 1;
445	return (m_iterNdx < m_numIters) ? CONTINUE : STOP;
446}
447
448//! Checks derivates when value depends on gl_HelperInvocation.
449class HelperInvocationDerivateCase : public TestCase
450{
451public:
452							HelperInvocationDerivateCase	(Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples, const char* derivateFunc, bool checkAbsoluteValue);
453							~HelperInvocationDerivateCase	(void);
454
455	void					init							(void);
456	void					deinit							(void);
457	IterateResult			iterate							(void);
458
459private:
460	const PrimitiveType		m_primitiveType;
461	const int				m_numSamples;
462	const std::string		m_derivateFunc;
463	const bool				m_checkAbsoluteValue;
464
465	const int				m_numIters;
466
467	MovePtr<ShaderProgram>	m_program;
468	MovePtr<FboHelper>		m_fbo;
469	int						m_iterNdx;
470};
471
472HelperInvocationDerivateCase::HelperInvocationDerivateCase (Context& context, const char* name, const char* description, PrimitiveType primType, int numSamples, const char* derivateFunc, bool checkAbsoluteValue)
473	: TestCase					(context, name, description)
474	, m_primitiveType			(primType)
475	, m_numSamples				(numSamples)
476	, m_derivateFunc			(derivateFunc)
477	, m_checkAbsoluteValue		(checkAbsoluteValue)
478	, m_numIters				(16)
479	, m_iterNdx					(0)
480{
481}
482
483HelperInvocationDerivateCase::~HelperInvocationDerivateCase (void)
484{
485	deinit();
486}
487
488void HelperInvocationDerivateCase::init (void)
489{
490	const glu::RenderContext&	renderCtx		= m_context.getRenderContext();
491	const glw::Functions&		gl				= renderCtx.getFunctions();
492	const int					maxSamples		= getInteger(gl, GL_MAX_SAMPLES);
493	const int					actualSamples	= m_numSamples == NUM_SAMPLES_MAX ? maxSamples : m_numSamples;
494	const std::string			funcSource		= (m_checkAbsoluteValue) ? ("abs(" + m_derivateFunc + "(value))") : (m_derivateFunc + "(value)");
495
496	m_program = MovePtr<ShaderProgram>(new ShaderProgram(m_context.getRenderContext(),
497		glu::ProgramSources()
498			<< glu::VertexSource(
499				"#version 310 es\n"
500				"in highp vec2 a_position;\n"
501				"uniform highp float u_pointSize;\n"
502				"void main (void)\n"
503				"{\n"
504				"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
505				"	gl_PointSize = u_pointSize;\n"
506				"}\n")
507			<< glu::FragmentSource(string(
508				"#version 310 es\n"
509				"out mediump vec4 o_color;\n"
510				"void main (void)\n"
511				"{\n"
512				"	highp float value		= gl_HelperInvocation ? 1.0 : 0.0;\n"
513				"	highp float derivate	= ") + funcSource + ";\n"
514				"	if (gl_HelperInvocation)\n"
515				"		o_color = vec4(1.0, 0.0, derivate, 1.0);\n"
516				"	else\n"
517				"		o_color = vec4(0.0, 1.0, derivate, 1.0);\n"
518				"}\n")));
519
520	m_testCtx.getLog() << *m_program;
521
522	if (!m_program->isOk())
523	{
524		m_program.clear();
525		TCU_FAIL("Compile failed");
526	}
527
528	m_testCtx.getLog() << TestLog::Message << "Using GL_RGBA8 framebuffer with "
529					   << actualSamples << " samples" << TestLog::EndMessage;
530
531	m_fbo = MovePtr<FboHelper>(new FboHelper(renderCtx, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT,
532											 FRAMEBUFFER_FORMAT, actualSamples));
533
534	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
535}
536
537void HelperInvocationDerivateCase::deinit (void)
538{
539	m_program.clear();
540	m_fbo.clear();
541}
542
543static bool hasNeighborWithColor (const tcu::Surface& surface, int x, int y, tcu::RGBA color, tcu::RGBA threshold)
544{
545	static const IVec2 s_neighbors[] =
546	{
547		IVec2(-1, -1),
548		IVec2( 0, -1),
549		IVec2(+1, -1),
550		IVec2(-1,  0),
551		IVec2(+1,  0),
552		IVec2(-1, +1),
553		IVec2( 0, +1),
554		IVec2(+1, +1)
555	};
556
557	const int	w	= surface.getWidth();
558	const int	h	= surface.getHeight();
559
560	for (int sample = 0; sample < DE_LENGTH_OF_ARRAY(s_neighbors); sample++)
561	{
562		const IVec2	pos	= IVec2(x, y) + s_neighbors[sample];
563
564		if (de::inBounds(pos.x(), 0, w) && de::inBounds(pos.y(), 0, h))
565		{
566			const tcu::RGBA neighborColor = surface.getPixel(pos.x(), pos.y());
567
568			if (tcu::compareThreshold(color, neighborColor, threshold))
569				return true;
570		}
571		else
572			return true; // Can't know for certain
573	}
574
575	return false;
576}
577
578static bool verifyHelperInvocationDerivate (TestLog& log, const tcu::Surface& result, bool isMultiSample)
579{
580	const tcu::RGBA		bgRef				(0, 0, 0, 255);
581	const tcu::RGBA		fgRef				(0, 255, 0, 255);
582	const tcu::RGBA		isBgThreshold		(1, isMultiSample ? 254 : 1, 0, 1);
583	const tcu::RGBA		isFgThreshold		(1, isMultiSample ? 254 : 1, 255, 1);
584	int					numInvalidPixels	= 0;
585	int					numNonZeroDeriv		= 0;
586	bool				renderedSomething	= false;
587
588	for (int y = 0; y < result.getHeight(); ++y)
589	{
590		for (int x = 0; x < result.getWidth(); ++x)
591		{
592			const tcu::RGBA	resPix			= result.getPixel(x, y);
593			const bool		isBg			= tcu::compareThreshold(resPix, bgRef, isBgThreshold);
594			const bool		isFg			= tcu::compareThreshold(resPix, fgRef, isFgThreshold);
595			const bool		nonZeroDeriv	= resPix.getBlue() > 0;
596			const bool		neighborBg		= nonZeroDeriv ? hasNeighborWithColor(result, x, y, bgRef, isBgThreshold) : false;
597
598			if (nonZeroDeriv)
599				numNonZeroDeriv	+= 1;
600
601			if ((!isBg && !isFg) ||				// Neither of valid colors (ignoring blue channel that has derivate)
602				(nonZeroDeriv && !neighborBg))	// Has non-zero derivate, but sample not at primitive edge
603				numInvalidPixels += 1;
604
605			if (isFg)
606				renderedSomething = true;
607		}
608	}
609
610	log << TestLog::Message << "Found " << numNonZeroDeriv << " pixels with non-zero derivate (neighbor sample has gl_HelperInvocation = true)" << TestLog::EndMessage;
611
612	if (numInvalidPixels > 0)
613	{
614		log << TestLog::Image("Result", "Result image", result);
615		log << TestLog::Message << "ERROR: Found " << numInvalidPixels << " invalid result pixels!" << TestLog::EndMessage;
616		return false;
617	}
618	else if (!renderedSomething)
619	{
620		log << TestLog::Image("Result", "Result image", result);
621		log << TestLog::Message << "ERROR: Result image was empty!" << TestLog::EndMessage;
622		return false;
623	}
624	else
625	{
626		log << TestLog::Message << "All result pixels are valid" << TestLog::EndMessage;
627		return true;
628	}
629}
630
631HelperInvocationDerivateCase::IterateResult HelperInvocationDerivateCase::iterate (void)
632{
633	const glu::RenderContext&		renderCtx	= m_context.getRenderContext();
634	const glw::Functions&			gl			= renderCtx.getFunctions();
635	const string					sectionName	= string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(m_numIters);
636	const tcu::ScopedLogSection		section		(m_testCtx.getLog(), (string("Iter") + de::toString(m_iterNdx)), sectionName);
637	de::Random						rnd			(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
638	tcu::Surface					result		(FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT);
639
640	m_fbo->bindForRendering();
641	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
642	gl.clear(GL_COLOR_BUFFER_BIT);
643
644	drawRandomPrimitives(renderCtx, m_program->getProgram(), m_primitiveType, 1, &rnd);
645
646	m_fbo->readPixels(0, 0, result.getAccess());
647
648	if (!verifyHelperInvocationDerivate(m_testCtx.getLog(), result, m_numSamples != 0))
649		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid pixels found");
650
651	m_iterNdx += 1;
652	return (m_iterNdx < m_numIters) ? CONTINUE : STOP;
653}
654
655} // anonymous
656
657ShaderHelperInvocationTests::ShaderHelperInvocationTests (Context& context)
658	: TestCaseGroup(context, "helper_invocation", "gl_HelperInvocation tests")
659{
660}
661
662ShaderHelperInvocationTests::~ShaderHelperInvocationTests (void)
663{
664}
665
666void ShaderHelperInvocationTests::init (void)
667{
668	static const struct
669	{
670		const char*		caseName;
671		PrimitiveType	primType;
672	} s_primTypes[] =
673	{
674		{ "triangles",		PRIMITIVETYPE_TRIANGLE		},
675		{ "lines",			PRIMITIVETYPE_LINE			},
676		{ "wide_lines",		PRIMITIVETYPE_WIDE_LINE		},
677		{ "points",			PRIMITIVETYPE_POINT			},
678		{ "wide_points",	PRIMITIVETYPE_WIDE_POINT	}
679	};
680
681	static const struct
682	{
683		const char*		suffix;
684		int				numSamples;
685	} s_sampleCounts[] =
686	{
687		{ "",					0				},
688		{ "_4_samples",			4				},
689		{ "_8_samples",			8				},
690		{ "_max_samples",		NUM_SAMPLES_MAX	}
691	};
692
693	// value
694	{
695		tcu::TestCaseGroup* const valueGroup = new tcu::TestCaseGroup(m_testCtx, "value", "gl_HelperInvocation value in rendered pixels");
696		addChild(valueGroup);
697
698		for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(s_sampleCounts); sampleCountNdx++)
699		{
700			for (int primTypeNdx = 0; primTypeNdx < DE_LENGTH_OF_ARRAY(s_primTypes); primTypeNdx++)
701			{
702				const string		name		= string(s_primTypes[primTypeNdx].caseName) + s_sampleCounts[sampleCountNdx].suffix;
703				const PrimitiveType	primType	= s_primTypes[primTypeNdx].primType;
704				const int			numSamples	= s_sampleCounts[sampleCountNdx].numSamples;
705
706				valueGroup->addChild(new HelperInvocationValueCase(m_context, name.c_str(), "", primType, numSamples));
707			}
708		}
709	}
710
711	// derivate
712	{
713		tcu::TestCaseGroup* const derivateGroup = new tcu::TestCaseGroup(m_testCtx, "derivate", "Derivate of gl_HelperInvocation-dependent value");
714		addChild(derivateGroup);
715
716		for (int sampleCountNdx = 0; sampleCountNdx < DE_LENGTH_OF_ARRAY(s_sampleCounts); sampleCountNdx++)
717		{
718			for (int primTypeNdx = 0; primTypeNdx < DE_LENGTH_OF_ARRAY(s_primTypes); primTypeNdx++)
719			{
720				const string		name		= string(s_primTypes[primTypeNdx].caseName) + s_sampleCounts[sampleCountNdx].suffix;
721				const PrimitiveType	primType	= s_primTypes[primTypeNdx].primType;
722				const int			numSamples	= s_sampleCounts[sampleCountNdx].numSamples;
723
724				derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_dfdx").c_str(),	"", primType, numSamples, "dFdx",	true));
725				derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_dfdy").c_str(),	"", primType, numSamples, "dFdy",	true));
726				derivateGroup->addChild(new HelperInvocationDerivateCase(m_context, (name + "_fwidth").c_str(),	"", primType, numSamples, "fwidth",	false));
727			}
728		}
729	}
730}
731
732} // Functional
733} // gles31
734} // deqp
735