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 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);
  
  			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);
  			}
  
  			// avoid generating narrow triangles
  			{
  				const int	maxAttempts	= 40;
  				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
  }
  
  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 float				threshold	= 0.02f;
  
  	const tcu::PixelFormat	pixelFmt	= getPixelFormat(egl, display, config.config);
  	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);
  
  	// 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 float				threshold			= 0.02f;
  
  	const tcu::PixelFormat	pixelFmt			= getPixelFormat(egl, display, config.config);
  	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);
  
  	// 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
  