text: /*-------------------------------------------------------------------------
   * drawElements Quality Program EGL Module
   * ---------------------------------------
   *
   * Copyright 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   *//*!
   * \file
   * \brief Rendering tests for different config and api combinations.
   * \todo [2013-03-19 pyry] GLES1 and VG support.
   *//*--------------------------------------------------------------------*/
  
  #include "teglRenderTests.hpp"
  #include "teglRenderCase.hpp"
  
  #include "tcuRenderTarget.hpp"
  #include "tcuTestLog.hpp"
  #include "tcuImageCompare.hpp"
  #include "tcuTextureUtil.hpp"
  #include "tcuSurface.hpp"
  
  #include "egluDefs.hpp"
  #include "egluUtil.hpp"
  
  #include "eglwLibrary.hpp"
  #include "eglwEnums.hpp"
  
  #include "gluShaderProgram.hpp"
  
  #include "glwFunctions.hpp"
  #include "glwEnums.hpp"
  
  #include "deRandom.hpp"
  #include "deSharedPtr.hpp"
  #include "deSemaphore.hpp"
  #include "deThread.hpp"
  #include "deString.h"
  
  #include "rrRenderer.hpp"
  #include "rrFragmentOperations.hpp"
  
  #include <algorithm>
  #include <iterator>
  #include <memory>
  #include <set>
  
  namespace deqp
  {
  namespace egl
  {
  
  using std::string;
  using std::vector;
  using std::set;
  
  using tcu::Vec4;
  
  using tcu::TestLog;
  
  using namespace glw;
  using namespace eglw;
  
  static const tcu::Vec4	CLEAR_COLOR		= tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
  static const float		CLEAR_DEPTH		= 1.0f;
  static const int		CLEAR_STENCIL	= 0;
  
  namespace
  {
  
  enum PrimitiveType
  {
  	PRIMITIVETYPE_TRIANGLE = 0,	//!< Triangles, requires 3 coordinates per primitive
  //	PRIMITIVETYPE_POINT,		//!< Points, requires 1 coordinate per primitive (w is used as size)
  //	PRIMITIVETYPE_LINE,			//!< Lines, requires 2 coordinates per primitive
  
  	PRIMITIVETYPE_LAST
  };
  
  enum BlendMode
  {
  	BLENDMODE_NONE = 0,			//!< No blending
  	BLENDMODE_ADDITIVE,			//!< Blending with ONE, ONE
  	BLENDMODE_SRC_OVER,			//!< Blending with SRC_ALPHA, ONE_MINUS_SRC_ALPHA
  
  	BLENDMODE_LAST
  };
  
  enum DepthMode
  {
  	DEPTHMODE_NONE = 0,			//!< No depth test or depth writes
  	DEPTHMODE_LESS,				//!< Depth test with less & depth write
  
  	DEPTHMODE_LAST
  };
  
  enum StencilMode
  {
  	STENCILMODE_NONE = 0,		//!< No stencil test or write
  	STENCILMODE_LEQUAL_INC,		//!< Stencil test with LEQUAL, increment on pass
  
  	STENCILMODE_LAST
  };
  
  struct DrawPrimitiveOp
  {
  	PrimitiveType	type;
  	int				count;
  	vector<Vec4>	positions;
  	vector<Vec4>	colors;
  	BlendMode		blend;
  	DepthMode		depth;
  	StencilMode		stencil;
  	int				stencilRef;
  };
  
  static bool isANarrowScreenSpaceTriangle (const tcu::Vec4& p0, const tcu::Vec4& p1, const tcu::Vec4& p2)
  {
  	// to clip space
  	const tcu::Vec2	csp0				= p0.swizzle(0, 1) / p0.w();
  	const tcu::Vec2	csp1				= p1.swizzle(0, 1) / p1.w();
  	const tcu::Vec2	csp2				= p2.swizzle(0, 1) / p2.w();
  
  	const tcu::Vec2	e01					= (csp1 - csp0);
  	const tcu::Vec2	e02					= (csp2 - csp0);
  
  	const float		minimumVisibleArea	= 0.4f; // must cover at least 10% of the surface
  	const float		visibleArea			= de::abs(e01.x() * e02.y() - e02.x() * e01.y()) * 0.5f;
  
  	return visibleArea < minimumVisibleArea;
  }
  
  void randomizeDrawOp (de::Random& rnd, DrawPrimitiveOp& drawOp, const bool alphaZeroOrOne)
  {
  	const int	minStencilRef	= 0;
  	const int	maxStencilRef	= 8;
  	const int	minPrimitives	= 2;
  	const int	maxPrimitives	= 4;
  
  	const float	maxTriOffset	= 1.0f;
  	const float	minDepth		= -1.0f; // \todo [pyry] Reference doesn't support Z clipping yet
  	const float	maxDepth		= 1.0f;
  
  	const float	minRGB			= 0.2f;
  	const float	maxRGB			= 0.9f;
  	const float	minAlpha		= 0.3f;
  	const float	maxAlpha		= 1.0f;
  
  	drawOp.type			= (PrimitiveType)rnd.getInt(0, PRIMITIVETYPE_LAST-1);
  	drawOp.count		= rnd.getInt(minPrimitives, maxPrimitives);
  	drawOp.blend		= (BlendMode)rnd.getInt(0, BLENDMODE_LAST-1);
  	drawOp.depth		= (DepthMode)rnd.getInt(0, DEPTHMODE_LAST-1);
  	drawOp.stencil		= (StencilMode)rnd.getInt(0, STENCILMODE_LAST-1);
  	drawOp.stencilRef	= rnd.getInt(minStencilRef, maxStencilRef);
  
  	if (drawOp.type == PRIMITIVETYPE_TRIANGLE)
  	{
  		drawOp.positions.resize(drawOp.count*3);
  		drawOp.colors.resize(drawOp.count*3);
  
  		for (int triNdx = 0; triNdx < drawOp.count; triNdx++)
  		{
  			const float		cx		= rnd.getFloat(-1.0f, 1.0f);
  			const float		cy		= rnd.getFloat(-1.0f, 1.0f);
  			const float		flatAlpha = (rnd.getFloat(minAlpha, maxAlpha) > 0.5f) ? 1.0f : 0.0f;
  
  			for (int coordNdx = 0; coordNdx < 3; coordNdx++)
  			{
  				tcu::Vec4&	position	= drawOp.positions[triNdx*3 + coordNdx];
  				tcu::Vec4&	color		= drawOp.colors[triNdx*3 + coordNdx];
  
  				position.x()	= cx + rnd.getFloat(-maxTriOffset, maxTriOffset);
  				position.y()	= cy + rnd.getFloat(-maxTriOffset, maxTriOffset);
  				position.z()	= rnd.getFloat(minDepth, maxDepth);
  				position.w()	= 1.0f;
  
  				color.x()		= rnd.getFloat(minRGB, maxRGB);
  				color.y()		= rnd.getFloat(minRGB, maxRGB);
  				color.z()		= rnd.getFloat(minRGB, maxRGB);
  				color.w()		= rnd.getFloat(minAlpha, maxAlpha);
  
  				if (alphaZeroOrOne)
  				{
  					color.w()	= flatAlpha;
  				}
  			}
  
  			// avoid generating narrow triangles
  			{
  				const int	maxAttempts	= 100;
  				int			numAttempts	= 0;
  				tcu::Vec4&	p0			= drawOp.positions[triNdx*3 + 0];
  				tcu::Vec4&	p1			= drawOp.positions[triNdx*3 + 1];
  				tcu::Vec4&	p2			= drawOp.positions[triNdx*3 + 2];
  
  				while (isANarrowScreenSpaceTriangle(p0, p1, p2))
  				{
  					p1.x()	= cx + rnd.getFloat(-maxTriOffset, maxTriOffset);
  					p1.y()	= cy + rnd.getFloat(-maxTriOffset, maxTriOffset);
  					p1.z()	= rnd.getFloat(minDepth, maxDepth);
  					p1.w()	= 1.0f;
  
  					p2.x()	= cx + rnd.getFloat(-maxTriOffset, maxTriOffset);
  					p2.y()	= cy + rnd.getFloat(-maxTriOffset, maxTriOffset);
  					p2.z()	= rnd.getFloat(minDepth, maxDepth);
  					p2.w()	= 1.0f;
  
  					if (++numAttempts > maxAttempts)
  					{
  						DE_ASSERT(false);
  						break;
  					}
  				}
  			}
  		}
  	}
  	else
  		DE_ASSERT(false);
  }
  
  // Reference rendering code
  
  class ReferenceShader : public rr::VertexShader, public rr::FragmentShader
  {
  public:
  	enum
  	{
  		VaryingLoc_Color = 0
  	};
  
  	ReferenceShader ()
  		: rr::VertexShader	(2, 1)		// color and pos in => color out
  		, rr::FragmentShader(1, 1)		// color in => color out
  	{
  		this->rr::VertexShader::m_inputs[0].type		= rr::GENERICVECTYPE_FLOAT;
  		this->rr::VertexShader::m_inputs[1].type		= rr::GENERICVECTYPE_FLOAT;
  
  		this->rr::VertexShader::m_outputs[0].type		= rr::GENERICVECTYPE_FLOAT;
  		this->rr::VertexShader::m_outputs[0].flatshade	= false;
  
  		this->rr::FragmentShader::m_inputs[0].type		= rr::GENERICVECTYPE_FLOAT;
  		this->rr::FragmentShader::m_inputs[0].flatshade	= false;
  
  		this->rr::FragmentShader::m_outputs[0].type		= rr::GENERICVECTYPE_FLOAT;
  	}
  
  	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
  	{
  		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
  		{
  			const int positionAttrLoc = 0;
  			const int colorAttrLoc = 1;
  
  			rr::VertexPacket& packet = *packets[packetNdx];
  
  			// Transform to position
  			packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx);
  
  			// Pass color to FS
  			packet.outputs[VaryingLoc_Color] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx);
  		}
  	}
  
  	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
  	{
  		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
  		{
  			rr::FragmentPacket& packet = packets[packetNdx];
  
  			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
  				rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packet, context, VaryingLoc_Color, fragNdx));
  		}
  	}
  };
  
  void toReferenceRenderState (rr::RenderState& state, const DrawPrimitiveOp& drawOp)
  {
  	state.cullMode	= rr::CULLMODE_NONE;
  
  	if (drawOp.blend != BLENDMODE_NONE)
  	{
  		state.fragOps.blendMode = rr::BLENDMODE_STANDARD;
  
  		switch (drawOp.blend)
  		{
  			case BLENDMODE_ADDITIVE:
  				state.fragOps.blendRGBState.srcFunc		= rr::BLENDFUNC_ONE;
  				state.fragOps.blendRGBState.dstFunc		= rr::BLENDFUNC_ONE;
  				state.fragOps.blendRGBState.equation	= rr::BLENDEQUATION_ADD;
  				state.fragOps.blendAState				= state.fragOps.blendRGBState;
  				break;
  
  			case BLENDMODE_SRC_OVER:
  				state.fragOps.blendRGBState.srcFunc		= rr::BLENDFUNC_SRC_ALPHA;
  				state.fragOps.blendRGBState.dstFunc		= rr::BLENDFUNC_ONE_MINUS_SRC_ALPHA;
  				state.fragOps.blendRGBState.equation	= rr::BLENDEQUATION_ADD;
  				state.fragOps.blendAState				= state.fragOps.blendRGBState;
  				break;
  
  			default:
  				DE_ASSERT(false);
  		}
  	}
  
  	if (drawOp.depth != DEPTHMODE_NONE)
  	{
  		state.fragOps.depthTestEnabled = true;
  
  		DE_ASSERT(drawOp.depth == DEPTHMODE_LESS);
  		state.fragOps.depthFunc = rr::TESTFUNC_LESS;
  	}
  
  	if (drawOp.stencil != STENCILMODE_NONE)
  	{
  		state.fragOps.stencilTestEnabled = true;
  
  		DE_ASSERT(drawOp.stencil == STENCILMODE_LEQUAL_INC);
  		state.fragOps.stencilStates[0].func		= rr::TESTFUNC_LEQUAL;
  		state.fragOps.stencilStates[0].sFail	= rr::STENCILOP_KEEP;
  		state.fragOps.stencilStates[0].dpFail	= rr::STENCILOP_INCR;
  		state.fragOps.stencilStates[0].dpPass	= rr::STENCILOP_INCR;
  		state.fragOps.stencilStates[0].ref		= drawOp.stencilRef;
  		state.fragOps.stencilStates[1]			= state.fragOps.stencilStates[0];
  	}
  }
  
  tcu::TextureFormat getColorFormat (const tcu::PixelFormat& colorBits)
  {
  	using tcu::TextureFormat;
  
  	DE_ASSERT(de::inBounds(colorBits.redBits,	0, 0xff) &&
  			  de::inBounds(colorBits.greenBits,	0, 0xff) &&
  			  de::inBounds(colorBits.blueBits,	0, 0xff) &&
  			  de::inBounds(colorBits.alphaBits,	0, 0xff));
  
  #define PACK_FMT(R, G, B, A) (((R) << 24) | ((G) << 16) | ((B) << 8) | (A))
  
  	// \note [pyry] This may not hold true on some implementations - best effort guess only.
  	switch (PACK_FMT(colorBits.redBits, colorBits.greenBits, colorBits.blueBits, colorBits.alphaBits))
  	{
  		case PACK_FMT(8,8,8,8):		return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8);
  		case PACK_FMT(8,8,8,0):		return TextureFormat(TextureFormat::RGB,	TextureFormat::UNORM_INT8);
  		case PACK_FMT(4,4,4,4):		return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_SHORT_4444);
  		case PACK_FMT(5,5,5,1):		return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_SHORT_5551);
  		case PACK_FMT(5,6,5,0):		return TextureFormat(TextureFormat::RGB,	TextureFormat::UNORM_SHORT_565);
  
  		// \note Defaults to RGBA8
  		default:					return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8);
  	}
  
  #undef PACK_FMT
  }
  
  /*
  The getColorThreshold function is used to obtain a
  threshold usable for the fuzzyCompare function.
  
  For 8bit color depths a value of 0.02 should provide
  a good metric for rejecting images above this level.
  For other bit depths other thresholds should be selected.
  Ideally this function would take advantage of the
  getColorThreshold function provided by the PixelFormat class
  as this would also allow setting per channel thresholds.
  However using the PixelFormat provided function can result
  in too strict thresholds for 8bit bit depths (compared to
  the current default of 0.02) or too relaxed for lower bit
  depths if scaled proportionally to the 8bit default.
  */
  
  float getColorThreshold (const tcu::PixelFormat& colorBits)
  {
  	if ((colorBits.redBits > 0 && colorBits.redBits < 8) ||
  		(colorBits.greenBits > 0 && colorBits.greenBits < 8) ||
  		(colorBits.blueBits > 0 && colorBits.blueBits < 8) ||
  		(colorBits.alphaBits > 0 && colorBits.alphaBits < 8))
  	{
  		return 0.05f;
  	}
  	else
  	{
  		return 0.02f;
  	}
  }
  
  tcu::TextureFormat getDepthFormat (const int depthBits)
  {
  	switch (depthBits)
  	{
  		case 0:		return tcu::TextureFormat();
  		case 8:		return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT8);
  		case 16:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16);
  		case 24:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT24);
  		case 32:
  		default:	return tcu::TextureFormat(tcu::TextureFormat::D, tcu::TextureFormat::FLOAT);
  	}
  }
  
  tcu::TextureFormat getStencilFormat (int stencilBits)
  {
  	switch (stencilBits)
  	{
  		case 0:		return tcu::TextureFormat();
  		case 8:
  		default:	return tcu::TextureFormat(tcu::TextureFormat::S, tcu::TextureFormat::UNSIGNED_INT8);
  	}
  }
  
  void renderReference (const tcu::PixelBufferAccess& dst, const vector<DrawPrimitiveOp>& drawOps, const tcu::PixelFormat& colorBits, const int depthBits, const int stencilBits, const int numSamples)
  {
  	const int						width			= dst.getWidth();
  	const int						height			= dst.getHeight();
  
  	tcu::TextureLevel				colorBuffer;
  	tcu::TextureLevel				depthBuffer;
  	tcu::TextureLevel				stencilBuffer;
  
  	rr::Renderer					referenceRenderer;
  	rr::VertexAttrib				attributes[2];
  	const ReferenceShader			shader;
  
  	attributes[0].type				= rr::VERTEXATTRIBTYPE_FLOAT;
  	attributes[0].size				= 4;
  	attributes[0].stride			= 0;
  	attributes[0].instanceDivisor	= 0;
  
  	attributes[1].type				= rr::VERTEXATTRIBTYPE_FLOAT;
  	attributes[1].size				= 4;
  	attributes[1].stride			= 0;
  	attributes[1].instanceDivisor	= 0;
  
  	// Initialize buffers.
  	colorBuffer.setStorage(getColorFormat(colorBits), numSamples, width, height);
  	rr::clearMultisampleColorBuffer(colorBuffer, CLEAR_COLOR, rr::WindowRectangle(0, 0, width, height));
  
  	if (depthBits > 0)
  	{
  		depthBuffer.setStorage(getDepthFormat(depthBits), numSamples, width, height);
  		rr::clearMultisampleDepthBuffer(depthBuffer, CLEAR_DEPTH, rr::WindowRectangle(0, 0, width, height));
  	}
  
  	if (stencilBits > 0)
  	{
  		stencilBuffer.setStorage(getStencilFormat(stencilBits), numSamples, width, height);
  		rr::clearMultisampleStencilBuffer(stencilBuffer, CLEAR_STENCIL, rr::WindowRectangle(0, 0, width, height));
  	}
  
  	const rr::RenderTarget renderTarget(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess()),
  										rr::MultisamplePixelBufferAccess::fromMultisampleAccess(depthBuffer.getAccess()),
  										rr::MultisamplePixelBufferAccess::fromMultisampleAccess(stencilBuffer.getAccess()));
  
  	for (vector<DrawPrimitiveOp>::const_iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); drawOp++)
  	{
  		// Translate state
  		rr::RenderState renderState((rr::ViewportState)(rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess())));
  		toReferenceRenderState(renderState, *drawOp);
  
  		DE_ASSERT(drawOp->type == PRIMITIVETYPE_TRIANGLE);
  
  		attributes[0].pointer = &drawOp->positions[0];
  		attributes[1].pointer = &drawOp->colors[0];
  
  		referenceRenderer.draw(
  			rr::DrawCommand(
  				renderState,
  				renderTarget,
  				rr::Program(static_cast<const rr::VertexShader*>(&shader), static_cast<const rr::FragmentShader*>(&shader)),
  				2,
  				attributes,
  				rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, drawOp->count * 3, 0)));
  	}
  
  	rr::resolveMultisampleColorBuffer(dst, rr::MultisamplePixelBufferAccess::fromMultisampleAccess(colorBuffer.getAccess()));
  }
  
  // API rendering code
  
  class Program
  {
  public:
  					Program				(void) {}
  	virtual			~Program			(void) {}
  
  	virtual void	setup				(void) const = DE_NULL;
  };
  
  typedef de::SharedPtr<Program> ProgramSp;
  
  static glu::ProgramSources getProgramSourcesES2 (void)
  {
  	static const char* s_vertexSrc =
  		"attribute highp vec4 a_position;\n"
  		"attribute mediump vec4 a_color;\n"
  		"varying mediump vec4 v_color;\n"
  		"void main (void)\n"
  		"{\n"
  		"	gl_Position = a_position;\n"
  		"	v_color = a_color;\n"
  		"}\n";
  
  	static const char* s_fragmentSrc =
  		"varying mediump vec4 v_color;\n"
  		"void main (void)\n"
  		"{\n"
  		"	gl_FragColor = v_color;\n"
  		"}\n";
  
  	return glu::ProgramSources() << glu::VertexSource(s_vertexSrc) << glu::FragmentSource(s_fragmentSrc);
  }
  
  class GLES2Program : public Program
  {
  public:
  	GLES2Program (const glw::Functions& gl)
  		: m_gl				(gl)
  		, m_program			(gl, getProgramSourcesES2())
  		, m_positionLoc		(0)
  		, m_colorLoc		(0)
  	{
  
  		m_positionLoc	= m_gl.getAttribLocation(m_program.getProgram(), "a_position");
  		m_colorLoc		= m_gl.getAttribLocation(m_program.getProgram(), "a_color");
  	}
  
  	~GLES2Program (void)
  	{
  	}
  
  	void setup (void) const
  	{
  		m_gl.useProgram(m_program.getProgram());
  		m_gl.enableVertexAttribArray(m_positionLoc);
  		m_gl.enableVertexAttribArray(m_colorLoc);
  		GLU_CHECK_GLW_MSG(m_gl, "Program setup failed");
  	}
  
  	int						getPositionLoc		(void) const { return m_positionLoc;	}
  	int						getColorLoc			(void) const { return m_colorLoc;		}
  
  private:
  	const glw::Functions&	m_gl;
  	glu::ShaderProgram		m_program;
  	int						m_positionLoc;
  	int						m_colorLoc;
  };
  
  void clearGLES2 (const glw::Functions& gl, const tcu::Vec4& color, const float depth, const int stencil)
  {
  	gl.clearColor(color.x(), color.y(), color.z(), color.w());
  	gl.clearDepthf(depth);
  	gl.clearStencil(stencil);
  	gl.clear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
  }
  
  void drawGLES2 (const glw::Functions& gl, const Program& program, const DrawPrimitiveOp& drawOp)
  {
  	const GLES2Program& gles2Program = dynamic_cast<const GLES2Program&>(program);
  
  	switch (drawOp.blend)
  	{
  		case BLENDMODE_NONE:
  			gl.disable(GL_BLEND);
  			break;
  
  		case BLENDMODE_ADDITIVE:
  			gl.enable(GL_BLEND);
  			gl.blendFunc(GL_ONE, GL_ONE);
  			break;
  
  		case BLENDMODE_SRC_OVER:
  			gl.enable(GL_BLEND);
  			gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  			break;
  
  		default:
  			DE_ASSERT(false);
  	}
  
  	switch (drawOp.depth)
  	{
  		case DEPTHMODE_NONE:
  			gl.disable(GL_DEPTH_TEST);
  			break;
  
  		case DEPTHMODE_LESS:
  			gl.enable(GL_DEPTH_TEST);
  			break;
  
  		default:
  			DE_ASSERT(false);
  	}
  
  	switch (drawOp.stencil)
  	{
  		case STENCILMODE_NONE:
  			gl.disable(GL_STENCIL_TEST);
  			break;
  
  		case STENCILMODE_LEQUAL_INC:
  			gl.enable(GL_STENCIL_TEST);
  			gl.stencilFunc(GL_LEQUAL, drawOp.stencilRef, ~0u);
  			gl.stencilOp(GL_KEEP, GL_INCR, GL_INCR);
  			break;
  
  		default:
  			DE_ASSERT(false);
  	}
  
  	gl.disable(GL_DITHER);
  
  	gl.vertexAttribPointer(gles2Program.getPositionLoc(), 4, GL_FLOAT, GL_FALSE, 0, &drawOp.positions[0]);
  	gl.vertexAttribPointer(gles2Program.getColorLoc(), 4, GL_FLOAT, GL_FALSE, 0, &drawOp.colors[0]);
  
  	DE_ASSERT(drawOp.type == PRIMITIVETYPE_TRIANGLE);
  	gl.drawArrays(GL_TRIANGLES, 0, drawOp.count*3);
  }
  
  static void readPixelsGLES2 (const glw::Functions& gl, tcu::Surface& dst)
  {
  	gl.readPixels(0, 0, dst.getWidth(), dst.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, dst.getAccess().getDataPtr());
  }
  
  Program* createProgram (const glw::Functions& gl, EGLint api)
  {
  	switch (api)
  	{
  		case EGL_OPENGL_ES2_BIT:		return new GLES2Program(gl);
  		case EGL_OPENGL_ES3_BIT_KHR:	return new GLES2Program(gl);
  		default:
  			throw tcu::NotSupportedError("Unsupported API");
  	}
  }
  
  void draw (const glw::Functions& gl, EGLint api, const Program& program, const DrawPrimitiveOp& drawOp)
  {
  	switch (api)
  	{
  		case EGL_OPENGL_ES2_BIT:		drawGLES2(gl, program, drawOp);		break;
  		case EGL_OPENGL_ES3_BIT_KHR:	drawGLES2(gl, program, drawOp);		break;
  		default:
  			throw tcu::NotSupportedError("Unsupported API");
  	}
  }
  
  void clear (const glw::Functions& gl, EGLint api, const tcu::Vec4& color, const float depth, const int stencil)
  {
  	switch (api)
  	{
  		case EGL_OPENGL_ES2_BIT:		clearGLES2(gl, color, depth, stencil);		break;
  		case EGL_OPENGL_ES3_BIT_KHR:	clearGLES2(gl, color, depth, stencil);		break;
  		default:
  			throw tcu::NotSupportedError("Unsupported API");
  	}
  }
  
  static void readPixels (const glw::Functions& gl, EGLint api, tcu::Surface& dst)
  {
  	switch (api)
  	{
  		case EGL_OPENGL_ES2_BIT:		readPixelsGLES2(gl, dst);		break;
  		case EGL_OPENGL_ES3_BIT_KHR:	readPixelsGLES2(gl, dst);		break;
  		default:
  			throw tcu::NotSupportedError("Unsupported API");
  	}
  }
  
  static void finish (const glw::Functions& gl, EGLint api)
  {
  	switch (api)
  	{
  		case EGL_OPENGL_ES2_BIT:
  		case EGL_OPENGL_ES3_BIT_KHR:
  			gl.finish();
  			break;
  
  		default:
  			throw tcu::NotSupportedError("Unsupported API");
  	}
  }
  
  tcu::PixelFormat getPixelFormat (const Library& egl, EGLDisplay display, EGLConfig config)
  {
  	tcu::PixelFormat fmt;
  	fmt.redBits		= eglu::getConfigAttribInt(egl, display, config, EGL_RED_SIZE);
  	fmt.greenBits	= eglu::getConfigAttribInt(egl, display, config, EGL_GREEN_SIZE);
  	fmt.blueBits	= eglu::getConfigAttribInt(egl, display, config, EGL_BLUE_SIZE);
  	fmt.alphaBits	= eglu::getConfigAttribInt(egl, display, config, EGL_ALPHA_SIZE);
  	return fmt;
  }
  
  } // anonymous
  
  // SingleThreadRenderCase
  
  class SingleThreadRenderCase : public MultiContextRenderCase
  {
  public:
  						SingleThreadRenderCase		(EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi);
  
  	void				init						(void);
  
  private:
  	virtual void		executeForContexts			(EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts);
  
  	glw::Functions		m_gl;
  };
  
  // SingleThreadColorClearCase
  
  SingleThreadRenderCase::SingleThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi)
  	: MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, filters, numContextsPerApi)
  {
  }
  
  void SingleThreadRenderCase::init (void)
  {
  	MultiContextRenderCase::init();
  	m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2,0));
  }
  
  void SingleThreadRenderCase::executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts)
  {
  	const Library&			egl			= m_eglTestCtx.getLibrary();
  	const int				width		= eglu::querySurfaceInt(egl, display, surface, EGL_WIDTH);
  	const int				height		= eglu::querySurfaceInt(egl, display, surface, EGL_HEIGHT);
  	const int				numContexts	= (int)contexts.size();
  	const int				drawsPerCtx	= 2;
  	const int				numIters	= 2;
  	const tcu::PixelFormat	pixelFmt	= getPixelFormat(egl, display, config.config);
  	const float				threshold	= getColorThreshold(pixelFmt);
  
  	const int				depthBits	= eglu::getConfigAttribInt(egl, display, config.config, EGL_DEPTH_SIZE);
  	const int				stencilBits	= eglu::getConfigAttribInt(egl, display, config.config, EGL_STENCIL_SIZE);
  	const int				numSamples	= eglu::getConfigAttribInt(egl, display, config.config, EGL_SAMPLES);
  
  	TestLog&				log			= m_testCtx.getLog();
  
  	tcu::Surface			refFrame	(width, height);
  	tcu::Surface			frame		(width, height);
  
  	de::Random				rnd			(deStringHash(getName()) ^ deInt32Hash(numContexts));
  	vector<ProgramSp>		programs	(contexts.size());
  	vector<DrawPrimitiveOp>	drawOps;
  
  	// Log basic information about config.
  	log << TestLog::Message << "EGL_RED_SIZE = "		<< pixelFmt.redBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_GREEN_SIZE = "		<< pixelFmt.greenBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_BLUE_SIZE = "		<< pixelFmt.blueBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_ALPHA_SIZE = "		<< pixelFmt.alphaBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_DEPTH_SIZE = "		<< depthBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_STENCIL_SIZE = "	<< stencilBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_SAMPLES = "			<< numSamples << TestLog::EndMessage;
  
  	// Generate draw ops.
  	drawOps.resize(numContexts*drawsPerCtx*numIters);
  	for (vector<DrawPrimitiveOp>::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp)
  		randomizeDrawOp(rnd, *drawOp, (pixelFmt.alphaBits == 1));
  
  	// Create and setup programs per context
  	for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++)
  	{
  		EGLint		api			= contexts[ctxNdx].first;
  		EGLContext	context		= contexts[ctxNdx].second;
  
  		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
  
  		programs[ctxNdx] = ProgramSp(createProgram(m_gl, api));
  		programs[ctxNdx]->setup();
  	}
  
  	// Clear to black using first context.
  	{
  		EGLint		api			= contexts[0].first;
  		EGLContext	context		= contexts[0].second;
  
  		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
  
  		clear(m_gl, api, CLEAR_COLOR, CLEAR_DEPTH, CLEAR_STENCIL);
  		finish(m_gl, api);
  	}
  
  	// Render.
  	for (int iterNdx = 0; iterNdx < numIters; iterNdx++)
  	{
  		for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++)
  		{
  			EGLint		api			= contexts[ctxNdx].first;
  			EGLContext	context		= contexts[ctxNdx].second;
  
  			EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
  
  			for (int drawNdx = 0; drawNdx < drawsPerCtx; drawNdx++)
  			{
  				const DrawPrimitiveOp& drawOp = drawOps[iterNdx*numContexts*drawsPerCtx + ctxNdx*drawsPerCtx + drawNdx];
  				draw(m_gl, api, *programs[ctxNdx], drawOp);
  			}
  
  			finish(m_gl, api);
  		}
  	}
  
  	// Read pixels using first context. \todo [pyry] Randomize?
  	{
  		EGLint		api		= contexts[0].first;
  		EGLContext	context	= contexts[0].second;
  
  		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
  
  		readPixels(m_gl, api, frame);
  	}
  
  	EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
  
  	// Render reference.
  	// \note Reference image is always generated using single-sampling.
  	renderReference(refFrame.getAccess(), drawOps, pixelFmt, depthBits, stencilBits, 1);
  
  	// Compare images
  	{
  		bool imagesOk = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, threshold, tcu::COMPARE_LOG_RESULT);
  
  		if (!imagesOk)
  			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
  	}
  }
  
  // MultiThreadRenderCase
  
  class MultiThreadRenderCase : public MultiContextRenderCase
  {
  public:
  						MultiThreadRenderCase		(EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi);
  
  	void				init						(void);
  
  private:
  	virtual void		executeForContexts			(EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts);
  
  	glw::Functions		m_gl;
  };
  
  class RenderTestThread;
  
  typedef de::SharedPtr<RenderTestThread>	RenderTestThreadSp;
  typedef de::SharedPtr<de::Semaphore>	SemaphoreSp;
  
  struct DrawOpPacket
  {
  	DrawOpPacket (void)
  		: drawOps	(DE_NULL)
  		, numOps	(0)
  	{
  	}
  
  	const DrawPrimitiveOp*	drawOps;
  	int						numOps;
  	SemaphoreSp				wait;
  	SemaphoreSp				signal;
  };
  
  class RenderTestThread : public de::Thread
  {
  public:
  	RenderTestThread (const Library& egl, EGLDisplay display, EGLSurface surface, EGLContext context, EGLint api, const glw::Functions& gl, const Program& program, const std::vector<DrawOpPacket>& packets)
  		: m_egl		(egl)
  		, m_display	(display)
  		, m_surface	(surface)
  		, m_context	(context)
  		, m_api		(api)
  		, m_gl		(gl)
  		, m_program	(program)
  		, m_packets	(packets)
  	{
  	}
  
  	void run (void)
  	{
  		for (std::vector<DrawOpPacket>::const_iterator packetIter = m_packets.begin(); packetIter != m_packets.end(); packetIter++)
  		{
  			// Wait until it is our turn.
  			packetIter->wait->decrement();
  
  			// Acquire context.
  			EGLU_CHECK_CALL(m_egl, makeCurrent(m_display, m_surface, m_surface, m_context));
  
  			// Execute rendering.
  			for (int ndx = 0; ndx < packetIter->numOps; ndx++)
  				draw(m_gl, m_api, m_program, packetIter->drawOps[ndx]);
  
  			finish(m_gl, m_api);
  
  			// Release context.
  			EGLU_CHECK_CALL(m_egl, makeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
  
  			// Signal completion.
  			packetIter->signal->increment();
  		}
  		m_egl.releaseThread();
  	}
  
  private:
  	const Library&						m_egl;
  	EGLDisplay							m_display;
  	EGLSurface							m_surface;
  	EGLContext							m_context;
  	EGLint								m_api;
  	const glw::Functions&				m_gl;
  	const Program&						m_program;
  	const std::vector<DrawOpPacket>&	m_packets;
  };
  
  MultiThreadRenderCase::MultiThreadRenderCase (EglTestContext& eglTestCtx, const char* name, const char* description, EGLint api, EGLint surfaceType, const eglu::FilterList& filters, int numContextsPerApi)
  	: MultiContextRenderCase(eglTestCtx, name, description, api, surfaceType, filters, numContextsPerApi)
  {
  }
  
  void MultiThreadRenderCase::init (void)
  {
  	MultiContextRenderCase::init();
  	m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2,0));
  }
  
  void MultiThreadRenderCase::executeForContexts (EGLDisplay display, EGLSurface surface, const Config& config, const std::vector<std::pair<EGLint, EGLContext> >& contexts)
  {
  	const Library&			egl					= m_eglTestCtx.getLibrary();
  	const int				width				= eglu::querySurfaceInt(egl, display, surface, EGL_WIDTH);
  	const int				height				= eglu::querySurfaceInt(egl, display, surface, EGL_HEIGHT);
  	const int				numContexts			= (int)contexts.size();
  	const int				opsPerPacket		= 2;
  	const int				packetsPerThread	= 2;
  	const int				numThreads			= numContexts;
  	const int				numPackets			= numThreads * packetsPerThread;
  	const tcu::PixelFormat	pixelFmt			= getPixelFormat(egl, display, config.config);
  	const float				threshold			= getColorThreshold(pixelFmt);
  
  	const int				depthBits			= eglu::getConfigAttribInt(egl, display, config.config, EGL_DEPTH_SIZE);
  	const int				stencilBits			= eglu::getConfigAttribInt(egl, display, config.config, EGL_STENCIL_SIZE);
  	const int				numSamples			= eglu::getConfigAttribInt(egl, display, config.config, EGL_SAMPLES);
  
  	TestLog&				log					= m_testCtx.getLog();
  
  	tcu::Surface			refFrame			(width, height);
  	tcu::Surface			frame				(width, height);
  
  	de::Random				rnd					(deStringHash(getName()) ^ deInt32Hash(numContexts));
  
  	// Resources that need cleanup
  	vector<ProgramSp>				programs	(numContexts);
  	vector<SemaphoreSp>				semaphores	(numPackets+1);
  	vector<DrawPrimitiveOp>			drawOps		(numPackets*opsPerPacket);
  	vector<vector<DrawOpPacket> >	packets		(numThreads);
  	vector<RenderTestThreadSp>		threads		(numThreads);
  
  	// Log basic information about config.
  	log << TestLog::Message << "EGL_RED_SIZE = "		<< pixelFmt.redBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_GREEN_SIZE = "		<< pixelFmt.greenBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_BLUE_SIZE = "		<< pixelFmt.blueBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_ALPHA_SIZE = "		<< pixelFmt.alphaBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_DEPTH_SIZE = "		<< depthBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_STENCIL_SIZE = "	<< stencilBits << TestLog::EndMessage;
  	log << TestLog::Message << "EGL_SAMPLES = "			<< numSamples << TestLog::EndMessage;
  
  	// Initialize semaphores.
  	for (vector<SemaphoreSp>::iterator sem = semaphores.begin(); sem != semaphores.end(); ++sem)
  		*sem = SemaphoreSp(new de::Semaphore(0));
  
  	// Create draw ops.
  	for (vector<DrawPrimitiveOp>::iterator drawOp = drawOps.begin(); drawOp != drawOps.end(); ++drawOp)
  		randomizeDrawOp(rnd, *drawOp, (pixelFmt.alphaBits == 1));
  
  	// Create packets.
  	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
  	{
  		packets[threadNdx].resize(packetsPerThread);
  
  		for (int packetNdx = 0; packetNdx < packetsPerThread; packetNdx++)
  		{
  			DrawOpPacket& packet = packets[threadNdx][packetNdx];
  
  			// Threads take turns with packets.
  			packet.wait		= semaphores[packetNdx*numThreads + threadNdx];
  			packet.signal	= semaphores[packetNdx*numThreads + threadNdx + 1];
  			packet.numOps	= opsPerPacket;
  			packet.drawOps	= &drawOps[(packetNdx*numThreads + threadNdx)*opsPerPacket];
  		}
  	}
  
  	// Create and setup programs per context
  	for (int ctxNdx = 0; ctxNdx < numContexts; ctxNdx++)
  	{
  		EGLint		api			= contexts[ctxNdx].first;
  		EGLContext	context		= contexts[ctxNdx].second;
  
  		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
  
  		programs[ctxNdx] = ProgramSp(createProgram(m_gl, api));
  		programs[ctxNdx]->setup();
  
  		// Release context
  		EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
  	}
  
  	// Clear to black using first context.
  	{
  		EGLint		api			= contexts[0].first;
  		EGLContext	context		= contexts[0].second;
  
  		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
  
  		clear(m_gl, api, CLEAR_COLOR, CLEAR_DEPTH, CLEAR_STENCIL);
  		finish(m_gl, api);
  
  		// Release context
  		EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
  	}
  
  	// Create and launch threads (actual rendering starts once first semaphore is signaled).
  	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
  	{
  		threads[threadNdx] = RenderTestThreadSp(new RenderTestThread(egl, display, surface, contexts[threadNdx].second, contexts[threadNdx].first, m_gl, *programs[threadNdx], packets[threadNdx]));
  		threads[threadNdx]->start();
  	}
  
  	// Signal start and wait until complete.
  	semaphores.front()->increment();
  	semaphores.back()->decrement();
  
  	// Read pixels using first context. \todo [pyry] Randomize?
  	{
  		EGLint		api		= contexts[0].first;
  		EGLContext	context	= contexts[0].second;
  
  		EGLU_CHECK_CALL(egl, makeCurrent(display, surface, surface, context));
  
  		readPixels(m_gl, api, frame);
  	}
  
  	EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
  
  	// Join threads.
  	for (int threadNdx = 0; threadNdx < numThreads; threadNdx++)
  		threads[threadNdx]->join();
  
  	// Render reference.
  	renderReference(refFrame.getAccess(), drawOps, pixelFmt, depthBits, stencilBits, 1);
  
  	// Compare images
  	{
  		bool imagesOk = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refFrame, frame, threshold, tcu::COMPARE_LOG_RESULT);
  
  		if (!imagesOk)
  			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
  	}
  }
  
  RenderTests::RenderTests (EglTestContext& eglTestCtx)
  	: TestCaseGroup(eglTestCtx, "render", "Basic rendering with different client APIs")
  {
  }
  
  RenderTests::~RenderTests (void)
  {
  }
  
  struct RenderGroupSpec
  {
  	const char*			name;
  	const char*			desc;
  	EGLint				apiBits;
  	eglu::ConfigFilter	baseFilter;
  	int					numContextsPerApi;
  };
  
  template <deUint32 Bits>
  static bool renderable (const eglu::CandidateConfig& c)
  {
  	return (c.renderableType() & Bits) == Bits;
  }
  
  template <class RenderClass>
  static void createRenderGroups (EglTestContext& eglTestCtx, tcu::TestCaseGroup* group, const RenderGroupSpec* first, const RenderGroupSpec* last)
  {
  	for (const RenderGroupSpec* groupIter = first; groupIter != last; groupIter++)
  	{
  		tcu::TestCaseGroup* configGroup = new tcu::TestCaseGroup(eglTestCtx.getTestContext(), groupIter->name, groupIter->desc);
  		group->addChild(configGroup);
  
  		vector<RenderFilterList>	filterLists;
  		eglu::FilterList			baseFilters;
  		baseFilters << groupIter->baseFilter;
  		getDefaultRenderFilterLists(filterLists, baseFilters);
  
  		for (vector<RenderFilterList>::const_iterator listIter = filterLists.begin(); listIter != filterLists.end(); listIter++)
  			configGroup->addChild(new RenderClass(eglTestCtx, listIter->getName(), "", groupIter->apiBits, listIter->getSurfaceTypeMask(), *listIter, groupIter->numContextsPerApi));
  	}
  }
  
  void RenderTests::init (void)
  {
  	static const RenderGroupSpec singleContextCases[] =
  	{
  		{
  			"gles2",
  			"Primitive rendering using GLES2",
  			EGL_OPENGL_ES2_BIT,
  			renderable<EGL_OPENGL_ES2_BIT>,
  			1
  		},
  		{
  			"gles3",
  			"Primitive rendering using GLES3",
  			EGL_OPENGL_ES3_BIT,
  			renderable<EGL_OPENGL_ES3_BIT>,
  			1
  		},
  	};
  
  	static const RenderGroupSpec multiContextCases[] =
  	{
  		{
  			"gles2",
  			"Primitive rendering using multiple GLES2 contexts to shared surface",
  			EGL_OPENGL_ES2_BIT,
  			renderable<EGL_OPENGL_ES2_BIT>,
  			3
  		},
  		{
  			"gles3",
  			"Primitive rendering using multiple GLES3 contexts to shared surface",
  			EGL_OPENGL_ES3_BIT,
  			renderable<EGL_OPENGL_ES3_BIT>,
  			3
  		},
  		{
  			"gles2_gles3",
  			"Primitive rendering using multiple APIs to shared surface",
  			EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT,
  			renderable<EGL_OPENGL_ES2_BIT|EGL_OPENGL_ES3_BIT>,
  			1
  		},
  	};
  
  	tcu::TestCaseGroup* singleContextGroup = new tcu::TestCaseGroup(m_testCtx, "single_context", "Single-context rendering");
  	addChild(singleContextGroup);
  	createRenderGroups<SingleThreadRenderCase>(m_eglTestCtx, singleContextGroup, &singleContextCases[0], &singleContextCases[DE_LENGTH_OF_ARRAY(singleContextCases)]);
  
  	tcu::TestCaseGroup* multiContextGroup = new tcu::TestCaseGroup(m_testCtx, "multi_context", "Multi-context rendering with shared surface");
  	addChild(multiContextGroup);
  	createRenderGroups<SingleThreadRenderCase>(m_eglTestCtx, multiContextGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]);
  
  	tcu::TestCaseGroup* multiThreadGroup = new tcu::TestCaseGroup(m_testCtx, "multi_thread", "Multi-thread rendering with shared surface");
  	addChild(multiThreadGroup);
  	createRenderGroups<MultiThreadRenderCase>(m_eglTestCtx, multiThreadGroup, &multiContextCases[0], &multiContextCases[DE_LENGTH_OF_ARRAY(multiContextCases)]);
  }
  
  } // egl
  } // deqp
  