1/*-------------------------------------------------------------------------
2 * drawElements Quality Program EGL 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 Rendering tests for different config and api combinations.
22 * \todo [2013-03-19 pyry] GLES1 and VG support.
23 *//*--------------------------------------------------------------------*/
24
25#include "teglRenderTests.hpp"
26#include "teglRenderCase.hpp"
27
28#include "tcuRenderTarget.hpp"
29#include "tcuTestLog.hpp"
30#include "tcuImageCompare.hpp"
31#include "tcuTextureUtil.hpp"
32#include "tcuSurface.hpp"
33
34#include "egluDefs.hpp"
35#include "egluUtil.hpp"
36
37#include "eglwLibrary.hpp"
38#include "eglwEnums.hpp"
39
40#include "gluShaderProgram.hpp"
41
42#include "glwFunctions.hpp"
43#include "glwEnums.hpp"
44
45#include "deRandom.hpp"
46#include "deSharedPtr.hpp"
47#include "deSemaphore.hpp"
48#include "deThread.hpp"
49#include "deString.h"
50
51#include "rrRenderer.hpp"
52#include "rrFragmentOperations.hpp"
53
54#include <algorithm>
55#include <iterator>
56#include <memory>
57#include <set>
58
59namespace deqp
60{
61namespace egl
62{
63
64using std::string;
65using std::vector;
66using std::set;
67
68using tcu::Vec4;
69
70using tcu::TestLog;
71
72using namespace glw;
73using namespace eglw;
74
75static const tcu::Vec4	CLEAR_COLOR		= tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
76static const float		CLEAR_DEPTH		= 1.0f;
77static const int		CLEAR_STENCIL	= 0;
78
79namespace
80{
81
82enum PrimitiveType
83{
84	PRIMITIVETYPE_TRIANGLE = 0,	//!< Triangles, requires 3 coordinates per primitive
85//	PRIMITIVETYPE_POINT,		//!< Points, requires 1 coordinate per primitive (w is used as size)
86//	PRIMITIVETYPE_LINE,			//!< Lines, requires 2 coordinates per primitive
87
88	PRIMITIVETYPE_LAST
89};
90
91enum BlendMode
92{
93	BLENDMODE_NONE = 0,			//!< No blending
94	BLENDMODE_ADDITIVE,			//!< Blending with ONE, ONE
95	BLENDMODE_SRC_OVER,			//!< Blending with SRC_ALPHA, ONE_MINUS_SRC_ALPHA
96
97	BLENDMODE_LAST
98};
99
100enum DepthMode
101{
102	DEPTHMODE_NONE = 0,			//!< No depth test or depth writes
103	DEPTHMODE_LESS,				//!< Depth test with less & depth write
104
105	DEPTHMODE_LAST
106};
107
108enum StencilMode
109{
110	STENCILMODE_NONE = 0,		//!< No stencil test or write
111	STENCILMODE_LEQUAL_INC,		//!< Stencil test with LEQUAL, increment on pass
112
113	STENCILMODE_LAST
114};
115
116struct DrawPrimitiveOp
117{
118	PrimitiveType	type;
119	int				count;
120	vector<Vec4>	positions;
121	vector<Vec4>	colors;
122	BlendMode		blend;
123	DepthMode		depth;
124	StencilMode		stencil;
125	int				stencilRef;
126};
127
128static bool isANarrowScreenSpaceTriangle (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2)
129{
130	// to clip space
131	const tcu::Vec2	csp0				= p0.swizzle(0, 1) / p0.w();
132	const tcu::Vec2	csp1				= p1.swizzle(0, 1) / p1.w();
133	const tcu::Vec2	csp2				= p2.swizzle(0, 1) / p2.w();
134
135	const tcu::Vec2	e01					= (csp1 - csp0);
136	const tcu::Vec2	e02					= (csp2 - csp0);
137
138	const float		minimumVisibleArea	= 0.4f; // must cover at least 10% of the surface
139	const float		visibleArea			= de::abs(e01.x() * e02.y() - e02.x() * e01.y()) * 0.5f;
140
141	return visibleArea < minimumVisibleArea;
142}
143
144void randomizeDrawOp (de::Random& rnd, DrawPrimitiveOp& drawOp)
145{
146	const int	minStencilRef	= 0;
147	const int	maxStencilRef	= 8;
148	const int	minPrimitives	= 2;
149	const int	maxPrimitives	= 4;
150
151	const float	maxTriOffset	= 1.0f;
152	const float	minDepth		= -1.0f; // \todo [pyry] Reference doesn't support Z clipping yet
153	const float	maxDepth		= 1.0f;
154
155	const float	minRGB			= 0.2f;
156	const float	maxRGB			= 0.9f;
157	const float	minAlpha		= 0.3f;
158	const float	maxAlpha		= 1.0f;
159
160	drawOp.type			= (PrimitiveType)rnd.getInt(0, PRIMITIVETYPE_LAST-1);
161	drawOp.count		= rnd.getInt(minPrimitives, maxPrimitives);
162	drawOp.blend		= (BlendMode)rnd.getInt(0, BLENDMODE_LAST-1);
163	drawOp.depth		= (DepthMode)rnd.getInt(0, DEPTHMODE_LAST-1);
164	drawOp.stencil		= (StencilMode)rnd.getInt(0, STENCILMODE_LAST-1);
165	drawOp.stencilRef	= rnd.getInt(minStencilRef, maxStencilRef);
166
167	if (drawOp.type == PRIMITIVETYPE_TRIANGLE)
168	{
169		drawOp.positions.resize(drawOp.count*3);
170		drawOp.colors.resize(drawOp.count*3);
171
172		for (int triNdx = 0; triNdx < drawOp.count; triNdx++)
173		{
174			const float		cx		= rnd.getFloat(-1.0f, 1.0f);
175			const float		cy		= rnd.getFloat(-1.0f, 1.0f);
176
177			for (int coordNdx = 0; coordNdx < 3; coordNdx++)
178			{
179				tcu::Vec4&	position	= drawOp.positions[triNdx*3 + coordNdx];
180				tcu::Vec4&	color		= drawOp.colors[triNdx*3 + coordNdx];
181
182				position.x()	= cx + rnd.getFloat(-maxTriOffset, maxTriOffset);
183				position.y()	= cy + rnd.getFloat(-maxTriOffset, maxTriOffset);
184				position.z()	= rnd.getFloat(minDepth, maxDepth);
185				position.w()	= 1.0f;
186
187				color.x()		= rnd.getFloat(minRGB, maxRGB);
188				color.y()		= rnd.getFloat(minRGB, maxRGB);
189				color.z()		= rnd.getFloat(minRGB, maxRGB);
190				color.w()		= rnd.getFloat(minAlpha, maxAlpha);
191			}
192
193			// avoid generating narrow triangles
194			{
195				const int	maxAttempts	= 40;
196				int			numAttempts	= 0;
197				tcu::Vec4&	p0			= drawOp.positions[triNdx*3 + 0];
198				tcu::Vec4&	p1			= drawOp.positions[triNdx*3 + 1];
199				tcu::Vec4&	p2			= drawOp.positions[triNdx*3 + 2];
200
201				while (isANarrowScreenSpaceTriangle(p0, p1, p2))
202				{
203					p1.x()	= cx + rnd.getFloat(-maxTriOffset, maxTriOffset);
204					p1.y()	= cy + rnd.getFloat(-maxTriOffset, maxTriOffset);
205					p1.z()	= rnd.getFloat(minDepth, maxDepth);
206					p1.w()	= 1.0f;
207
208					p2.x()	= cx + rnd.getFloat(-maxTriOffset, maxTriOffset);
209					p2.y()	= cy + rnd.getFloat(-maxTriOffset, maxTriOffset);
210					p2.z()	= rnd.getFloat(minDepth, maxDepth);
211					p2.w()	= 1.0f;
212
213					if (++numAttempts > maxAttempts)
214					{
215						DE_ASSERT(false);
216						break;
217					}
218				}
219			}
220		}
221	}
222	else
223		DE_ASSERT(false);
224}
225
226// Reference rendering code
227
228class ReferenceShader : public rr::VertexShader, public rr::FragmentShader
229{
230public:
231	enum
232	{
233		VaryingLoc_Color = 0
234	};
235
236	ReferenceShader ()
237		: rr::VertexShader	(2, 1)		// color and pos in => color out
238		, rr::FragmentShader(1, 1)		// color in => color out
239	{
240		this->rr::VertexShader::m_inputs[0].type		= rr::GENERICVECTYPE_FLOAT;
241		this->rr::VertexShader::m_inputs[1].type		= rr::GENERICVECTYPE_FLOAT;
242
243		this->rr::VertexShader::m_outputs[0].type		= rr::GENERICVECTYPE_FLOAT;
244		this->rr::VertexShader::m_outputs[0].flatshade	= false;
245
246		this->rr::FragmentShader::m_inputs[0].type		= rr::GENERICVECTYPE_FLOAT;
247		this->rr::FragmentShader::m_inputs[0].flatshade	= false;
248
249		this->rr::FragmentShader::m_outputs[0].type		= rr::GENERICVECTYPE_FLOAT;
250	}
251
252	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
253	{
254		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
255		{
256			const int positionAttrLoc = 0;
257			const int colorAttrLoc = 1;
258
259			rr::VertexPacket& packet = *packets[packetNdx];
260
261			// Transform to position
262			packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx);
263
264			// Pass color to FS
265			packet.outputs[VaryingLoc_Color] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx);
266		}
267	}
268
269	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
270	{
271		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
272		{
273			rr::FragmentPacket& packet = packets[packetNdx];
274
275			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
276				rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packet, context, VaryingLoc_Color, fragNdx));
277		}
278	}
279};
280
281void toReferenceRenderState (rr::RenderState& state, const DrawPrimitiveOp& drawOp)
282{
283	state.cullMode	= rr::CULLMODE_NONE;
284
285	if (drawOp.blend != BLENDMODE_NONE)
286	{
287		state.fragOps.blendMode = rr::BLENDMODE_STANDARD;
288
289		switch (drawOp.blend)
290		{
291			case BLENDMODE_ADDITIVE:
292				state.fragOps.blendRGBState.srcFunc		= rr::BLENDFUNC_ONE;
293				state.fragOps.blendRGBState.dstFunc		= rr::BLENDFUNC_ONE;
294				state.fragOps.blendRGBState.equation	= rr::BLENDEQUATION_ADD;
295				state.fragOps.blendAState				= state.fragOps.blendRGBState;
296				break;
297
298			case BLENDMODE_SRC_OVER:
299				state.fragOps.blendRGBState.srcFunc		= rr::BLENDFUNC_SRC_ALPHA;
300				state.fragOps.blendRGBState.dstFunc		= rr::BLENDFUNC_ONE_MINUS_SRC_ALPHA;
301				state.fragOps.blendRGBState.equation	= rr::BLENDEQUATION_ADD;
302				state.fragOps.blendAState				= state.fragOps.blendRGBState;
303				break;
304
305			default:
306				DE_ASSERT(false);
307		}
308	}
309
310	if (drawOp.depth != DEPTHMODE_NONE)
311	{
312		state.fragOps.depthTestEnabled = true;
313
314		DE_ASSERT(drawOp.depth == DEPTHMODE_LESS);
315		state.fragOps.depthFunc = rr::TESTFUNC_LESS;
316	}
317
318	if (drawOp.stencil != STENCILMODE_NONE)
319	{
320		state.fragOps.stencilTestEnabled = true;
321
322		DE_ASSERT(drawOp.stencil == STENCILMODE_LEQUAL_INC);
323		state.fragOps.stencilStates[0].func		= rr::TESTFUNC_LEQUAL;
324		state.fragOps.stencilStates[0].sFail	= rr::STENCILOP_KEEP;
325		state.fragOps.stencilStates[0].dpFail	= rr::STENCILOP_INCR;
326		state.fragOps.stencilStates[0].dpPass	= rr::STENCILOP_INCR;
327		state.fragOps.stencilStates[0].ref		= drawOp.stencilRef;
328		state.fragOps.stencilStates[1]			= state.fragOps.stencilStates[0];
329	}
330}
331
332tcu::TextureFormat getColorFormat (const tcu::PixelFormat& colorBits)
333{
334	using tcu::TextureFormat;
335
336	DE_ASSERT(de::inBounds(colorBits.redBits,	0, 0xff) &&
337			  de::inBounds(colorBits.greenBits,	0, 0xff) &&
338			  de::inBounds(colorBits.blueBits,	0, 0xff) &&
339			  de::inBounds(colorBits.alphaBits,	0, 0xff));
340
341#define PACK_FMT(R, G, B, A) (((R) << 24) | ((G) << 16) | ((B) << 8) | (A))
342
343	// \note [pyry] This may not hold true on some implementations - best effort guess only.
344	switch (PACK_FMT(colorBits.redBits, colorBits.greenBits, colorBits.blueBits, colorBits.alphaBits))
345	{
346		case PACK_FMT(8,8,8,8):		return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8);
347		case PACK_FMT(8,8,8,0):		return TextureFormat(TextureFormat::RGB,	TextureFormat::UNORM_INT8);
348		case PACK_FMT(4,4,4,4):		return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_SHORT_4444);
349		case PACK_FMT(5,5,5,1):		return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_SHORT_5551);
350		case PACK_FMT(5,6,5,0):		return TextureFormat(TextureFormat::RGB,	TextureFormat::UNORM_SHORT_565);
351
352		// \note Defaults to RGBA8
353		default:					return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8);
354	}
355
356#undef PACK_FMT
357}
358
359/*
360The getColorThreshold function is used to obtain a
361threshold usable for the fuzzyCompare function.
362
363For 8bit color depths a value of 0.02 should provide
364a good metric for rejecting images above this level.
365For other bit depths other thresholds should be selected.
366Ideally this function would take advantage of the
367getColorThreshold function provided by the PixelFormat class
368as this would also allow setting per channel thresholds.
369However using the PixelFormat provided function can result
370in too strict thresholds for 8bit bit depths (compared to
371the current default of 0.02) or too relaxed for lower bit
372depths if scaled proportionally to the 8bit default.
373*/
374
375float getColorThreshold (const tcu::PixelFormat& colorBits)
376{
377	if ((colorBits.redBits > 0 && colorBits.redBits < 8) ||
378		(colorBits.greenBits > 0 && colorBits.greenBits < 8) ||
379		(colorBits.blueBits > 0 && colorBits.blueBits < 8) ||
380		(colorBits.alphaBits > 0 && colorBits.alphaBits < 8))
381	{
382		return 0.05f;
383	}
384	else
385	{
386		return 0.02f;
387	}
388}
389
390tcu::TextureFormat getDepthFormat (const int depthBits)
391{
392	switch (depthBits)
393	{
394		case 0:		return tcu::TextureFormat();
395		case 8:		return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT8);
396		case 16:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16);
397		case 24:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT24);
398		case 32:
399		default:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT);
400	}
401}
402
403tcu::TextureFormat getStencilFormat (int stencilBits)
404{
405	switch (stencilBits)
406	{
407		case 0:		return tcu::TextureFormat();
408		case 8:
409		default:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT8);
410	}
411}
412
413void renderReference (const tcu::PixelBufferAccess& dst, const vector<DrawPrimitiveOp>& drawOps, const tcu::PixelFormat& colorBits, const int depthBits, const int stencilBits, const int numSamples)
414{
415	const int						width			= dst.getWidth();
416	const int						height			= dst.getHeight();
417
418	tcu::TextureLevel				colorBuffer;
419	tcu::TextureLevel				depthBuffer;
420	tcu::TextureLevel				stencilBuffer;
421
422	rr::Renderer					referenceRenderer;
423	rr::VertexAttrib				attributes[2];
424	const ReferenceShader			shader;
425
426	attributes[0].type				= rr::VERTEXATTRIBTYPE_FLOAT;
427	attributes[0].size				= 4;
428	attributes[0].stride			= 0;
429	attributes[0].instanceDivisor	= 0;
430
431	attributes[1].type				= rr::VERTEXATTRIBTYPE_FLOAT;
432	attributes[1].size				= 4;
433	attributes[1].stride			= 0;
434	attributes[1].instanceDivisor	= 0;
435
436	// Initialize buffers.
437	colorBuffer.setStorage(getColorFormat(colorBits), numSamples, width, height);
438	rr::clearMultisampleColorBuffer(colorBuffer, CLEAR_COLOR, rr::WindowRectangle(0, 0, width, height));
439
440	if (depthBits > 0)
441	{
442		depthBuffer.setStorage(getDepthFormat(depthBits), numSamples, width, height);
443		rr::clearMultisampleDepthBuffer(depthBuffer, CLEAR_DEPTH, rr::WindowRectangle(0, 0, width, height));
444	}
445
446	if (stencilBits > 0)
447	{
448		stencilBuffer.setStorage(getStencilFormat(stencilBits), numSamples, width, height);
449		rr::clearMultisampleStencilBuffer(stencilBuffer, CLEAR_STENCIL, rr::WindowRectangle(0, 0, width, height));
450	}
451
452	const rr::RenderTarget renderTarget(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess()),
453										rr::MultisamplePixelBufferAccess::fromMultisampleAccess(depthBuffer.getAccess()),
454										rr::MultisamplePixelBufferAccess::fromMultisampleAccess(stencilBuffer.getAccess()));
455
456	for (vector<DrawPrimitiveOp>::const_iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); drawOp++)
457	{
458		// Translate state
459		rr::RenderState renderState((rr::ViewportState)(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess())));
460		toReferenceRenderState(renderState, *drawOp);
461
462		DE_ASSERT(drawOp->type == PRIMITIVETYPE_TRIANGLE);
463
464		attributes[0].pointer = &drawOp->positions[0];
465		attributes[1].pointer = &drawOp->colors[0];
466
467		referenceRenderer.draw(
468			rr::DrawCommand(
469				renderState,
470				renderTarget,
471				rr::Program(static_cast<const rr::VertexShader*>(&shader), static_cast<const rr::FragmentShader*>(&shader)),
472				2,
473				attributes,
474				rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, drawOp->count * 3, 0)));
475	}
476
477	rr::resolveMultisampleColorBuffer(dst, rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess()));
478}
479
480// API rendering code
481
482class Program
483{
484public:
485					Program				(void) {}
486	virtual			~Program			(void) {}
487
488	virtual void	setup				(void) const = DE_NULL;
489};
490
491typedef de::SharedPtr<Program> ProgramSp;
492
493static glu::ProgramSources getProgramSourcesES2 (void)
494{
495	static const char* s_vertexSrc =
496		"attribute highp vec4 a_position;\n"
497		"attribute mediump vec4 a_color;\n"
498		"varying mediump vec4 v_color;\n"
499		"void main (void)\n"
500		"{\n"
501		"	gl_Position = a_position;\n"
502		"	v_color = a_color;\n"
503		"}\n";
504
505	static const char* s_fragmentSrc =
506		"varying mediump vec4 v_color;\n"
507		"void main (void)\n"
508		"{\n"
509		"	gl_FragColor = v_color;\n"
510		"}\n";
511
512	return glu::ProgramSources() << glu::VertexSource(s_vertexSrc) << glu::FragmentSource(s_fragmentSrc);
513}
514
515class GLES2Program : public Program
516{
517public:
518	GLES2Program (const glw::Functions& gl)
519		: m_gl				(gl)
520		, m_program			(gl, getProgramSourcesES2())
521		, m_positionLoc		(0)
522		, m_colorLoc		(0)
523	{
524
525		m_positionLoc	= m_gl.getAttribLocation(m_program.getProgram(), "a_position");
526		m_colorLoc		= m_gl.getAttribLocation(m_program.getProgram(), "a_color");
527	}
528
529	~GLES2Program (void)
530	{
531	}
532
533	void setup (void) const
534	{
535		m_gl.useProgram(m_program.getProgram());
536		m_gl.enableVertexAttribArray(m_positionLoc);
537		m_gl.enableVertexAttribArray(m_colorLoc);
538		GLU_CHECK_GLW_MSG(m_gl, "Program setup failed");
539	}
540
541	int						getPositionLoc		(void) const { return m_positionLoc;	}
542	int						getColorLoc			(void) const { return m_colorLoc;		}
543
544private:
545	const glw::Functions&	m_gl;
546	glu::ShaderProgram		m_program;
547	int						m_positionLoc;
548	int						m_colorLoc;
549};
550
551void clearGLES2 (const glw::Functions& gl, const tcu::Vec4& color, const float depth, const int stencil)
552{
553	gl.clearColor(color.x(), color.y(), color.z(), color.w());
554	gl.clearDepthf(depth);
555	gl.clearStencil(stencil);
556	gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
557}
558
559void drawGLES2 (const glw::Functions& gl, const Program& program, const DrawPrimitiveOp& drawOp)
560{
561	const GLES2Program& gles2Program = dynamic_cast<const GLES2Program&>(program);
562
563	switch (drawOp.blend)
564	{
565		case BLENDMODE_NONE:
566			gl.disable(GL_BLEND);
567			break;
568
569		case BLENDMODE_ADDITIVE:
570			gl.enable(GL_BLEND);
571			gl.blendFunc(GL_ONE, GL_ONE);
572			break;
573
574		case BLENDMODE_SRC_OVER:
575			gl.enable(GL_BLEND);
576			gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
577			break;
578
579		default:
580			DE_ASSERT(false);
581	}
582
583	switch (drawOp.depth)
584	{
585		case DEPTHMODE_NONE:
586			gl.disable(GL_DEPTH_TEST);
587			break;
588
589		case DEPTHMODE_LESS:
590			gl.enable(GL_DEPTH_TEST);
591			break;
592
593		default:
594			DE_ASSERT(false);
595	}
596
597	switch (drawOp.stencil)
598	{
599		case STENCILMODE_NONE:
600			gl.disable(GL_STENCIL_TEST);
601			break;
602
603		case STENCILMODE_LEQUAL_INC:
604			gl.enable(GL_STENCIL_TEST);
605			gl.stencilFunc(GL_LEQUAL, drawOp.stencilRef, ~0u);
606			gl.stencilOp(GL_KEEP, GL_INCR, GL_INCR);
607			break;
608
609		default:
610			DE_ASSERT(false);
611	}
612
613	gl.disable(GL_DITHER);
614
615	gl.vertexAttribPointer(gles2Program.getPositionLoc(), 4, GL_FLOAT, GL_FALSE, 0, &drawOp.positions[0]);
616	gl.vertexAttribPointer(gles2Program.getColorLoc(), 4, GL_FLOAT, GL_FALSE, 0, &drawOp.colors[0]);
617
618	DE_ASSERT(drawOp.type == PRIMITIVETYPE_TRIANGLE);
619	gl.drawArrays(GL_TRIANGLES, 0, drawOp.count*3);
620}
621
622static void readPixelsGLES2 (const glw::Functions& gl, tcu::Surface& dst)
623{
624	gl.readPixels(0, 0, dst.getWidth(), dst.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, dst.getAccess().getDataPtr());
625}
626
627Program* createProgram (const glw::Functions& gl, EGLint api)
628{
629	switch (api)
630	{
631		case EGL_OPENGL_ES2_BIT:		return new GLES2Program(gl);
632		case EGL_OPENGL_ES3_BIT_KHR:	return new GLES2Program(gl);
633		default:
634			throw tcu::NotSupportedError("Unsupported API");
635	}
636}
637
638void draw (const glw::Functions& gl, EGLint api, const Program& program, const DrawPrimitiveOp& drawOp)
639{
640	switch (api)
641	{
642		case EGL_OPENGL_ES2_BIT:		drawGLES2(gl, program, drawOp);		break;
643		case EGL_OPENGL_ES3_BIT_KHR:	drawGLES2(gl, program, drawOp);		break;
644		default:
645			throw tcu::NotSupportedError("Unsupported API");
646	}
647}
648
649void clear (const glw::Functions& gl, EGLint api, const tcu::Vec4& color, const float depth, const int stencil)
650{
651	switch (api)
652	{
653		case EGL_OPENGL_ES2_BIT:		clearGLES2(gl, color, depth, stencil);		break;
654		case EGL_OPENGL_ES3_BIT_KHR:	clearGLES2(gl, color, depth, stencil);		break;
655		default:
656			throw tcu::NotSupportedError("Unsupported API");
657	}
658}
659
660static void readPixels (const glw::Functions& gl, EGLint api, tcu::Surface& dst)
661{
662	switch (api)
663	{
664		case EGL_OPENGL_ES2_BIT:		readPixelsGLES2(gl, dst);		break;
665		case EGL_OPENGL_ES3_BIT_KHR:	readPixelsGLES2(gl, dst);		break;
666		default:
667			throw tcu::NotSupportedError("Unsupported API");
668	}
669}
670
671static void finish (const glw::Functions& gl, EGLint api)
672{
673	switch (api)
674	{
675		case EGL_OPENGL_ES2_BIT:
676		case EGL_OPENGL_ES3_BIT_KHR:
677			gl.finish();
678			break;
679
680		default:
681			throw tcu::NotSupportedError("Unsupported API");
682	}
683}
684
685tcu::PixelFormat getPixelFormat (const Library& egl, EGLDisplay display, EGLConfig config)
686{
687	tcu::PixelFormat fmt;
688	fmt.redBits		= eglu::getConfigAttribInt(egl, display, config, EGL_RED_SIZE);
689	fmt.greenBits	= eglu::getConfigAttribInt(egl, display, config, EGL_GREEN_SIZE);
690	fmt.blueBits	= eglu::getConfigAttribInt(egl, display, config, EGL_BLUE_SIZE);
691	fmt.alphaBits	= eglu::getConfigAttribInt(egl, display, config, EGL_ALPHA_SIZE);
692	return fmt;
693}
694
695} // anonymous
696
697// SingleThreadRenderCase
698
699class SingleThreadRenderCase : public MultiContextRenderCase
700{
701public:
702						SingleThreadRenderCase		(EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi);
703
704	void				init						(void);
705
706private:
707	virtual void		executeForContexts			(EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts);
708
709	glw::Functions		m_gl;
710};
711
712// SingleThreadColorClearCase
713
714SingleThreadRenderCase::SingleThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi)
715	: MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, filters, numContextsPerApi)
716{
717}
718
719void SingleThreadRenderCase::init (void)
720{
721	MultiContextRenderCase::init();
722	m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2,0));
723}
724
725void SingleThreadRenderCase::executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts)
726{
727	const Library&			egl			= m_eglTestCtx.getLibrary();
728	const int				width		= eglu::querySurfaceInt(egl, display, surface, EGL_WIDTH);
729	const int				height		= eglu::querySurfaceInt(egl, display, surface, EGL_HEIGHT);
730	const int				numContexts	= (int)contexts.size();
731	const int				drawsPerCtx	= 2;
732	const int				numIters	= 2;
733	const tcu::PixelFormat	pixelFmt	= getPixelFormat(egl, display, config.config);
734	const float				threshold	= getColorThreshold(pixelFmt);
735
736	const int				depthBits	= eglu::getConfigAttribInt(egl, display, config.config, EGL_DEPTH_SIZE);
737	const int				stencilBits	= eglu::getConfigAttribInt(egl, display, config.config, EGL_STENCIL_SIZE);
738	const int				numSamples	= eglu::getConfigAttribInt(egl, display, config.config, EGL_SAMPLES);
739
740	TestLog&				log			= m_testCtx.getLog();
741
742	tcu::Surface			refFrame	(width, height);
743	tcu::Surface			frame		(width, height);
744
745	de::Random				rnd			(deStringHash(getName()) ^ deInt32Hash(numContexts));
746	vector<ProgramSp>		programs	(contexts.size());
747	vector<DrawPrimitiveOp>	drawOps;
748
749	// Log basic information about config.
750	log << TestLog::Message << "EGL_RED_SIZE = "		<< pixelFmt.redBits << TestLog::EndMessage;
751	log << TestLog::Message << "EGL_GREEN_SIZE = "		<< pixelFmt.greenBits << TestLog::EndMessage;
752	log << TestLog::Message << "EGL_BLUE_SIZE = "		<< pixelFmt.blueBits << TestLog::EndMessage;
753	log << TestLog::Message << "EGL_ALPHA_SIZE = "		<< pixelFmt.alphaBits << TestLog::EndMessage;
754	log << TestLog::Message << "EGL_DEPTH_SIZE = "		<< depthBits << TestLog::EndMessage;
755	log << TestLog::Message << "EGL_STENCIL_SIZE = "	<< stencilBits << TestLog::EndMessage;
756	log << TestLog::Message << "EGL_SAMPLES = "			<< numSamples << TestLog::EndMessage;
757
758	// Generate draw ops.
759	drawOps.resize(numContexts*drawsPerCtx*numIters);
760	for (vector<DrawPrimitiveOp>::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp)
761		randomizeDrawOp(rnd, *drawOp);
762
763	// Create and setup programs per context
764	for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++)
765	{
766		EGLint		api			= contexts[ctxNdx].first;
767		EGLContext	context		= contexts[ctxNdx].second;
768
769		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
770
771		programs[ctxNdx] = ProgramSp(createProgram(m_gl, api));
772		programs[ctxNdx]->setup();
773	}
774
775	// Clear to black using first context.
776	{
777		EGLint		api			= contexts[0].first;
778		EGLContext	context		= contexts[0].second;
779
780		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
781
782		clear(m_gl, api, CLEAR_COLOR, CLEAR_DEPTH, CLEAR_STENCIL);
783		finish(m_gl, api);
784	}
785
786	// Render.
787	for (int iterNdx = 0; iterNdx < numIters; iterNdx++)
788	{
789		for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++)
790		{
791			EGLint		api			= contexts[ctxNdx].first;
792			EGLContext	context		= contexts[ctxNdx].second;
793
794			EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
795
796			for (int drawNdx = 0; drawNdx < drawsPerCtx; drawNdx++)
797			{
798				const DrawPrimitiveOp& drawOp = drawOps[iterNdx*numContexts*drawsPerCtx + ctxNdx*drawsPerCtx + drawNdx];
799				draw(m_gl, api, *programs[ctxNdx], drawOp);
800			}
801
802			finish(m_gl, api);
803		}
804	}
805
806	// Read pixels using first context. \todo [pyry] Randomize?
807	{
808		EGLint		api		= contexts[0].first;
809		EGLContext	context	= contexts[0].second;
810
811		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
812
813		readPixels(m_gl, api, frame);
814	}
815
816	EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
817
818	// Render reference.
819	// \note Reference image is always generated using single-sampling.
820	renderReference(refFrame.getAccess(), drawOps, pixelFmt, depthBits, stencilBits, 1);
821
822	// Compare images
823	{
824		bool imagesOk = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, threshold, tcu::COMPARE_LOG_RESULT);
825
826		if (!imagesOk)
827			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
828	}
829}
830
831// MultiThreadRenderCase
832
833class MultiThreadRenderCase : public MultiContextRenderCase
834{
835public:
836						MultiThreadRenderCase		(EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi);
837
838	void				init						(void);
839
840private:
841	virtual void		executeForContexts			(EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts);
842
843	glw::Functions		m_gl;
844};
845
846class RenderTestThread;
847
848typedef de::SharedPtr<RenderTestThread>	RenderTestThreadSp;
849typedef de::SharedPtr<de::Semaphore>	SemaphoreSp;
850
851struct DrawOpPacket
852{
853	DrawOpPacket (void)
854		: drawOps	(DE_NULL)
855		, numOps	(0)
856	{
857	}
858
859	const DrawPrimitiveOp*	drawOps;
860	int						numOps;
861	SemaphoreSp				wait;
862	SemaphoreSp				signal;
863};
864
865class RenderTestThread : public de::Thread
866{
867public:
868	RenderTestThread (const Library& egl, EGLDisplay display, EGLSurface surface, EGLContext context, EGLint api, const glw::Functions& gl, const Program& program, const std::vector<DrawOpPacket>& packets)
869		: m_egl		(egl)
870		, m_display	(display)
871		, m_surface	(surface)
872		, m_context	(context)
873		, m_api		(api)
874		, m_gl		(gl)
875		, m_program	(program)
876		, m_packets	(packets)
877	{
878	}
879
880	void run (void)
881	{
882		for (std::vector<DrawOpPacket>::const_iterator packetIter = m_packets.begin(); packetIter != m_packets.end(); packetIter++)
883		{
884			// Wait until it is our turn.
885			packetIter->wait->decrement();
886
887			// Acquire context.
888			EGLU_CHECK_CALL(m_egl, makeCurrent(m_display, m_surface, m_surface, m_context));
889
890			// Execute rendering.
891			for (int ndx = 0; ndx < packetIter->numOps; ndx++)
892				draw(m_gl, m_api, m_program, packetIter->drawOps[ndx]);
893
894			finish(m_gl, m_api);
895
896			// Release context.
897			EGLU_CHECK_CALL(m_egl, makeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
898
899			// Signal completion.
900			packetIter->signal->increment();
901		}
902		m_egl.releaseThread();
903	}
904
905private:
906	const Library&						m_egl;
907	EGLDisplay							m_display;
908	EGLSurface							m_surface;
909	EGLContext							m_context;
910	EGLint								m_api;
911	const glw::Functions&				m_gl;
912	const Program&						m_program;
913	const std::vector<DrawOpPacket>&	m_packets;
914};
915
916MultiThreadRenderCase::MultiThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi)
917	: MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, filters, numContextsPerApi)
918{
919}
920
921void MultiThreadRenderCase::init (void)
922{
923	MultiContextRenderCase::init();
924	m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2,0));
925}
926
927void MultiThreadRenderCase::executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts)
928{
929	const Library&			egl					= m_eglTestCtx.getLibrary();
930	const int				width				= eglu::querySurfaceInt(egl, display, surface, EGL_WIDTH);
931	const int				height				= eglu::querySurfaceInt(egl, display, surface, EGL_HEIGHT);
932	const int				numContexts			= (int)contexts.size();
933	const int				opsPerPacket		= 2;
934	const int				packetsPerThread	= 2;
935	const int				numThreads			= numContexts;
936	const int				numPackets			= numThreads * packetsPerThread;
937	const tcu::PixelFormat	pixelFmt			= getPixelFormat(egl, display, config.config);
938	const float				threshold			= getColorThreshold(pixelFmt);
939
940	const int				depthBits			= eglu::getConfigAttribInt(egl, display, config.config, EGL_DEPTH_SIZE);
941	const int				stencilBits			= eglu::getConfigAttribInt(egl, display, config.config, EGL_STENCIL_SIZE);
942	const int				numSamples			= eglu::getConfigAttribInt(egl, display, config.config, EGL_SAMPLES);
943
944	TestLog&				log					= m_testCtx.getLog();
945
946	tcu::Surface			refFrame			(width, height);
947	tcu::Surface			frame				(width, height);
948
949	de::Random				rnd					(deStringHash(getName()) ^ deInt32Hash(numContexts));
950
951	// Resources that need cleanup
952	vector<ProgramSp>				programs	(numContexts);
953	vector<SemaphoreSp>				semaphores	(numPackets+1);
954	vector<DrawPrimitiveOp>			drawOps		(numPackets*opsPerPacket);
955	vector<vector<DrawOpPacket> >	packets		(numThreads);
956	vector<RenderTestThreadSp>		threads		(numThreads);
957
958	// Log basic information about config.
959	log << TestLog::Message << "EGL_RED_SIZE = "		<< pixelFmt.redBits << TestLog::EndMessage;
960	log << TestLog::Message << "EGL_GREEN_SIZE = "		<< pixelFmt.greenBits << TestLog::EndMessage;
961	log << TestLog::Message << "EGL_BLUE_SIZE = "		<< pixelFmt.blueBits << TestLog::EndMessage;
962	log << TestLog::Message << "EGL_ALPHA_SIZE = "		<< pixelFmt.alphaBits << TestLog::EndMessage;
963	log << TestLog::Message << "EGL_DEPTH_SIZE = "		<< depthBits << TestLog::EndMessage;
964	log << TestLog::Message << "EGL_STENCIL_SIZE = "	<< stencilBits << TestLog::EndMessage;
965	log << TestLog::Message << "EGL_SAMPLES = "			<< numSamples << TestLog::EndMessage;
966
967	// Initialize semaphores.
968	for (vector<SemaphoreSp>::iterator sem = semaphores.begin(); sem != semaphores.end(); ++sem)
969		*sem = SemaphoreSp(new de::Semaphore(0));
970
971	// Create draw ops.
972	for (vector<DrawPrimitiveOp>::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp)
973		randomizeDrawOp(rnd, *drawOp);
974
975	// Create packets.
976	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
977	{
978		packets[threadNdx].resize(packetsPerThread);
979
980		for (int packetNdx = 0; packetNdx < packetsPerThread; packetNdx++)
981		{
982			DrawOpPacket& packet = packets[threadNdx][packetNdx];
983
984			// Threads take turns with packets.
985			packet.wait		= semaphores[packetNdx*numThreads + threadNdx];
986			packet.signal	= semaphores[packetNdx*numThreads + threadNdx + 1];
987			packet.numOps	= opsPerPacket;
988			packet.drawOps	= &drawOps[(packetNdx*numThreads + threadNdx)*opsPerPacket];
989		}
990	}
991
992	// Create and setup programs per context
993	for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++)
994	{
995		EGLint		api			= contexts[ctxNdx].first;
996		EGLContext	context		= contexts[ctxNdx].second;
997
998		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
999
1000		programs[ctxNdx] = ProgramSp(createProgram(m_gl, api));
1001		programs[ctxNdx]->setup();
1002
1003		// Release context
1004		EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1005	}
1006
1007	// Clear to black using first context.
1008	{
1009		EGLint		api			= contexts[0].first;
1010		EGLContext	context		= contexts[0].second;
1011
1012		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
1013
1014		clear(m_gl, api, CLEAR_COLOR, CLEAR_DEPTH, CLEAR_STENCIL);
1015		finish(m_gl, api);
1016
1017		// Release context
1018		EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1019	}
1020
1021	// Create and launch threads (actual rendering starts once first semaphore is signaled).
1022	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
1023	{
1024		threads[threadNdx] = RenderTestThreadSp(new RenderTestThread(egl, display, surface, contexts[threadNdx].second, contexts[threadNdx].first, m_gl, *programs[threadNdx], packets[threadNdx]));
1025		threads[threadNdx]->start();
1026	}
1027
1028	// Signal start and wait until complete.
1029	semaphores.front()->increment();
1030	semaphores.back()->decrement();
1031
1032	// Read pixels using first context. \todo [pyry] Randomize?
1033	{
1034		EGLint		api		= contexts[0].first;
1035		EGLContext	context	= contexts[0].second;
1036
1037		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
1038
1039		readPixels(m_gl, api, frame);
1040	}
1041
1042	EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1043
1044	// Join threads.
1045	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
1046		threads[threadNdx]->join();
1047
1048	// Render reference.
1049	renderReference(refFrame.getAccess(), drawOps, pixelFmt, depthBits, stencilBits, 1);
1050
1051	// Compare images
1052	{
1053		bool imagesOk = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, threshold, tcu::COMPARE_LOG_RESULT);
1054
1055		if (!imagesOk)
1056			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
1057	}
1058}
1059
1060RenderTests::RenderTests (EglTestContext& eglTestCtx)
1061	: TestCaseGroup(eglTestCtx, "render", "Basic rendering with different client APIs")
1062{
1063}
1064
1065RenderTests::~RenderTests (void)
1066{
1067}
1068
1069struct RenderGroupSpec
1070{
1071	const char*			name;
1072	const char*			desc;
1073	EGLint				apiBits;
1074	eglu::ConfigFilter	baseFilter;
1075	int					numContextsPerApi;
1076};
1077
1078template <deUint32 Bits>
1079static bool renderable (const eglu::CandidateConfig& c)
1080{
1081	return (c.renderableType() & Bits) == Bits;
1082}
1083
1084template <class RenderClass>
1085static void createRenderGroups (EglTestContext& eglTestCtx, tcu::TestCaseGroup* group, const RenderGroupSpec* first, const RenderGroupSpec* last)
1086{
1087	for (const RenderGroupSpec* groupIter = first; groupIter != last; groupIter++)
1088	{
1089		tcu::TestCaseGroup* configGroup = new tcu::TestCaseGroup(eglTestCtx.getTestContext(), groupIter->name, groupIter->desc);
1090		group->addChild(configGroup);
1091
1092		vector<RenderFilterList>	filterLists;
1093		eglu::FilterList			baseFilters;
1094		baseFilters << groupIter->baseFilter;
1095		getDefaultRenderFilterLists(filterLists, baseFilters);
1096
1097		for (vector<RenderFilterList>::const_iterator listIter = filterLists.begin(); listIter != filterLists.end(); listIter++)
1098			configGroup->addChild(new RenderClass(eglTestCtx, listIter->getName(), "", groupIter->apiBits, listIter->getSurfaceTypeMask(), *listIter, groupIter->numContextsPerApi));
1099	}
1100}
1101
1102void RenderTests::init (void)
1103{
1104	static const RenderGroupSpec singleContextCases[] =
1105	{
1106		{
1107			"gles2",
1108			"Primitive rendering using GLES2",
1109			EGL_OPENGL_ES2_BIT,
1110			renderable<EGL_OPENGL_ES2_BIT>,
1111			1
1112		},
1113		{
1114			"gles3",
1115			"Primitive rendering using GLES3",
1116			EGL_OPENGL_ES3_BIT,
1117			renderable<EGL_OPENGL_ES3_BIT>,
1118			1
1119		},
1120	};
1121
1122	static const RenderGroupSpec multiContextCases[] =
1123	{
1124		{
1125			"gles2",
1126			"Primitive rendering using multiple GLES2 contexts to shared surface",
1127			EGL_OPENGL_ES2_BIT,
1128			renderable<EGL_OPENGL_ES2_BIT>,
1129			3
1130		},
1131		{
1132			"gles3",
1133			"Primitive rendering using multiple GLES3 contexts to shared surface",
1134			EGL_OPENGL_ES3_BIT,
1135			renderable<EGL_OPENGL_ES3_BIT>,
1136			3
1137		},
1138		{
1139			"gles2_gles3",
1140			"Primitive rendering using multiple APIs to shared surface",
1141			EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT,
1142			renderable<EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT>,
1143			1
1144		},
1145	};
1146
1147	tcu::TestCaseGroup* singleContextGroup = new tcu::TestCaseGroup(m_testCtx, "single_context", "Single-context rendering");
1148	addChild(singleContextGroup);
1149	createRenderGroups<SingleThreadRenderCase>(m_eglTestCtx, singleContextGroup, &singleContextCases[0], &singleContextCases[DE_LENGTH_OF_ARRAY(singleContextCases)]);
1150
1151	tcu::TestCaseGroup* multiContextGroup = new tcu::TestCaseGroup(m_testCtx, "multi_context", "Multi-context rendering with shared surface");
1152	addChild(multiContextGroup);
1153	createRenderGroups<SingleThreadRenderCase>(m_eglTestCtx, multiContextGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]);
1154
1155	tcu::TestCaseGroup* multiThreadGroup = new tcu::TestCaseGroup(m_testCtx, "multi_thread", "Multi-thread rendering with shared surface");
1156	addChild(multiThreadGroup);
1157	createRenderGroups<MultiThreadRenderCase>(m_eglTestCtx, multiThreadGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]);
1158}
1159
1160} // egl
1161} // deqp
1162