C++程序  |  7614行  |  291.31 KB

/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 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 Tessellation Tests.
 *//*--------------------------------------------------------------------*/

#include "es31fTessellationTests.hpp"
#include "glsTextureTestUtil.hpp"
#include "glsShaderLibrary.hpp"
#include "glsStateQueryUtil.hpp"
#include "gluShaderProgram.hpp"
#include "gluRenderContext.hpp"
#include "gluPixelTransfer.hpp"
#include "gluDrawUtil.hpp"
#include "gluObjectWrapper.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "gluVarType.hpp"
#include "gluVarTypeUtil.hpp"
#include "gluCallLogWrapper.hpp"
#include "tcuTestLog.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuImageIO.hpp"
#include "tcuResource.hpp"
#include "tcuImageCompare.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deSharedPtr.hpp"
#include "deString.h"
#include "deMath.h"

#include "glwEnums.hpp"
#include "glwDefs.hpp"
#include "glwFunctions.hpp"

#include <vector>
#include <string>
#include <algorithm>
#include <functional>
#include <set>
#include <limits>

using glu::ShaderProgram;
using glu::RenderContext;
using tcu::RenderTarget;
using tcu::TestLog;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using de::Random;
using de::SharedPtr;

using std::vector;
using std::string;

using namespace glw; // For GL types.

namespace deqp
{

using gls::TextureTestUtil::RandomViewport;

namespace gles31
{
namespace Functional
{

using namespace gls::StateQueryUtil;

enum
{
	MINIMUM_MAX_TESS_GEN_LEVEL = 64 //!< GL-defined minimum for GL_MAX_TESS_GEN_LEVEL.
};

static inline bool vec3XLessThan (const Vec3& a, const Vec3& b) { return a.x() < b.x(); }

template <typename IterT>
static string elemsStr (const IterT& begin, const IterT& end, int wrapLengthParam = 0, int numIndentationSpaces = 0)
{
	const string	baseIndentation	= string(numIndentationSpaces, ' ');
	const string	deepIndentation	= baseIndentation + string(4, ' ');
	const int		wrapLength		= wrapLengthParam > 0 ? wrapLengthParam : std::numeric_limits<int>::max();
	const int		length			= (int)std::distance(begin, end);
	string			result;

	if (length > wrapLength)
		result += "(amount: " + de::toString(length) + ") ";
	result += string() + "{" + (length > wrapLength ? "\n"+deepIndentation : " ");

	{
		int index = 0;
		for (IterT it = begin; it != end; ++it)
		{
			if (it != begin)
				result += string() + ", " + (index % wrapLength == 0 ? "\n"+deepIndentation : "");
			result += de::toString(*it);
			index++;
		}

		result += length > wrapLength ? "\n"+baseIndentation : " ";
	}

	result += "}";
	return result;
}

template <typename ContainerT>
static string containerStr (const ContainerT& c, int wrapLengthParam = 0, int numIndentationSpaces = 0)
{
	return elemsStr(c.begin(), c.end(), wrapLengthParam, numIndentationSpaces);
}

template <typename T, int N>
static string arrayStr (const T (&arr)[N], int wrapLengthParam = 0, int numIndentationSpaces = 0)
{
	return elemsStr(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr), wrapLengthParam, numIndentationSpaces);
}

template <typename T, int N>
static T arrayMax (const T (&arr)[N])
{
	return *std::max_element(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr));
}

template <typename T, typename MembT>
static vector<MembT> members (const vector<T>& objs, MembT T::* membP)
{
	vector<MembT> result(objs.size());
	for (int i = 0; i < (int)objs.size(); i++)
		result[i] = objs[i].*membP;
	return result;
}

template <typename T, int N>
static vector<T> arrayToVector (const T (&arr)[N])
{
	return vector<T>(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr));
}

template <typename ContainerT, typename T>
static inline bool contains (const ContainerT& c, const T& key)
{
	return c.find(key) != c.end();
}

template <int Size>
static inline tcu::Vector<bool, Size> singleTrueMask (int index)
{
	DE_ASSERT(de::inBounds(index, 0, Size));
	tcu::Vector<bool, Size> result;
	result[index] = true;
	return result;
}

static int intPow (int base, int exp)
{
	DE_ASSERT(exp >= 0);
	if (exp == 0)
		return 1;
	else
	{
		const int sub = intPow(base, exp/2);
		if (exp % 2 == 0)
			return sub*sub;
		else
			return sub*sub*base;
	}
}

tcu::Surface getPixels (const glu::RenderContext& rCtx, int x, int y, int width, int height)
{
	tcu::Surface result(width, height);
	glu::readPixels(rCtx, x, y, result.getAccess());
	return result;
}

tcu::Surface getPixels (const glu::RenderContext& rCtx, const RandomViewport& vp)
{
	return getPixels(rCtx, vp.x, vp.y, vp.width, vp.height);
}

static inline void checkRenderTargetSize (const RenderTarget& renderTarget, int minSize)
{
	if (renderTarget.getWidth() < minSize || renderTarget.getHeight() < minSize)
		throw tcu::NotSupportedError("Render target width and height must be at least " + de::toString(minSize));
}

tcu::TextureLevel getPNG (const tcu::Archive& archive, const string& filename)
{
	tcu::TextureLevel result;
	tcu::ImageIO::loadPNG(result, archive, filename.c_str());
	return result;
}

static int numBasicSubobjects (const glu::VarType& type)
{
	if (type.isBasicType())
		return 1;
	else if (type.isArrayType())
		return type.getArraySize()*numBasicSubobjects(type.getElementType());
	else if (type.isStructType())
	{
		const glu::StructType&	structType	= *type.getStructPtr();
		int						result		= 0;
		for (int i = 0; i < structType.getNumMembers(); i++)
			result += numBasicSubobjects(structType.getMember(i).getType());
		return result;
	}
	else
	{
		DE_ASSERT(false);
		return -1;
	}
}

static inline int numVerticesPerPrimitive (deUint32 primitiveTypeGL)
{
	switch (primitiveTypeGL)
	{
		case GL_POINTS:		return 1;
		case GL_TRIANGLES:	return 3;
		case GL_LINES:		return 2;
		default:
			DE_ASSERT(false);
			return -1;
	}
}

static inline void setViewport (const glw::Functions& gl, const RandomViewport& vp)
{
	gl.viewport(vp.x, vp.y, vp.width, vp.height);
}

static inline deUint32 getQueryResult (const glw::Functions& gl, deUint32 queryObject)
{
	deUint32 result = (deUint32)-1;
	gl.getQueryObjectuiv(queryObject, GL_QUERY_RESULT, &result);
	TCU_CHECK(result != (deUint32)-1);
	return result;
}

template <typename T>
static void readDataMapped (const glw::Functions& gl, deUint32 bufferTarget, int numElems, T* dst)
{
	const int							numBytes	= numElems*(int)sizeof(T);
	const T* const						mappedData	= (const T*)gl.mapBufferRange(bufferTarget, 0, numBytes, GL_MAP_READ_BIT);
	GLU_EXPECT_NO_ERROR(gl.getError(), (string() + "glMapBufferRange(" + glu::getBufferTargetName((int)bufferTarget) + ", 0, " + de::toString(numBytes) + ", GL_MAP_READ_BIT)").c_str());
	TCU_CHECK(mappedData != DE_NULL);

	for (int i = 0; i < numElems; i++)
		dst[i] = mappedData[i];

	gl.unmapBuffer(bufferTarget);
}

template <typename T>
static vector<T> readDataMapped (const glw::Functions& gl, deUint32 bufferTarget, int numElems)
{
	vector<T> result(numElems);
	readDataMapped(gl, bufferTarget, numElems, &result[0]);
	return result;
}

namespace
{

template <typename ArgT, bool res>
struct ConstantUnaryPredicate
{
	bool operator() (const ArgT&) const { return res; }
};

//! Helper for handling simple, one-varying transform feedbacks.
template <typename VaryingT>
class TransformFeedbackHandler
{
public:
	struct Result
	{
		int					numPrimitives;
		vector<VaryingT>	varying;

		Result (void)								: numPrimitives(-1) {}
		Result (int n, const vector<VaryingT>& v)	: numPrimitives(n), varying(v) {}
	};

									TransformFeedbackHandler	(const glu::RenderContext& renderCtx, int maxNumVertices);

	Result							renderAndGetPrimitives		(deUint32 programGL, deUint32 tfPrimTypeGL, int numBindings, const glu::VertexArrayBinding* bindings, int numVertices) const;

private:
	const glu::RenderContext&		m_renderCtx;
	const glu::TransformFeedback	m_tf;
	const glu::Buffer				m_tfBuffer;
	const glu::Query				m_tfPrimQuery;
};

template <typename AttribType>
TransformFeedbackHandler<AttribType>::TransformFeedbackHandler (const glu::RenderContext& renderCtx, int maxNumVertices)
	: m_renderCtx		(renderCtx)
	, m_tf				(renderCtx)
	, m_tfBuffer		(renderCtx)
	, m_tfPrimQuery		(renderCtx)
{
	const glw::Functions&	gl			= m_renderCtx.getFunctions();
	// \note Room for 1 extra triangle, to detect if GL returns too many primitives.
	const int				bufferSize	= (maxNumVertices + 3) * (int)sizeof(AttribType);

	gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, *m_tfBuffer);
	gl.bufferData(GL_TRANSFORM_FEEDBACK_BUFFER, bufferSize, DE_NULL, GL_DYNAMIC_READ);
}

template <typename AttribType>
typename TransformFeedbackHandler<AttribType>::Result TransformFeedbackHandler<AttribType>::renderAndGetPrimitives (deUint32 programGL, deUint32 tfPrimTypeGL, int numBindings, const glu::VertexArrayBinding* bindings, int numVertices) const
{
	DE_ASSERT(tfPrimTypeGL == GL_POINTS || tfPrimTypeGL == GL_LINES || tfPrimTypeGL == GL_TRIANGLES);

	const glw::Functions& gl = m_renderCtx.getFunctions();

	gl.bindTransformFeedback(GL_TRANSFORM_FEEDBACK, *m_tf);
	gl.bindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, *m_tfBuffer);
	gl.bindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, *m_tfBuffer);

	gl.beginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, *m_tfPrimQuery);
	gl.beginTransformFeedback(tfPrimTypeGL);

	glu::draw(m_renderCtx, programGL, numBindings, bindings, glu::pr::Patches(numVertices));
	GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");

	gl.endTransformFeedback();
	gl.endQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);

	{
		const int numPrimsWritten = (int)getQueryResult(gl, *m_tfPrimQuery);
		return Result(numPrimsWritten, readDataMapped<AttribType>(gl, GL_TRANSFORM_FEEDBACK_BUFFER, numPrimsWritten * numVerticesPerPrimitive(tfPrimTypeGL)));
	}
}

template <typename T>
class SizeLessThan
{
public:
	bool operator() (const T& a, const T& b) const { return a.size() < b.size(); }
};

//! Predicate functor for comparing structs by their members.
template <typename Pred, typename T, typename MembT>
class MemberPred
{
public:
				MemberPred	(MembT T::* membP) : m_membP(membP), m_pred(Pred()) {}
	bool		operator()	(const T& a, const T& b) const { return m_pred(a.*m_membP, b.*m_membP); }

private:
	MembT T::*	m_membP;
	Pred		m_pred;
};

//! Convenience wrapper for MemberPred, because class template arguments aren't deduced based on constructor arguments.
template <template <typename> class Pred, typename T, typename MembT>
static MemberPred<Pred<MembT>, T, MembT> memberPred (MembT T::* membP) { return MemberPred<Pred<MembT>, T, MembT>(membP); }

template <typename SeqT, int Size, typename Pred>
class LexCompare
{
public:
	LexCompare (void) : m_pred(Pred()) {}

	bool operator() (const SeqT& a, const SeqT& b) const
	{
		for (int i = 0; i < Size; i++)
		{
			if (m_pred(a[i], b[i]))
				return true;
			if (m_pred(b[i], a[i]))
				return false;
		}
		return false;
	}

private:
	Pred m_pred;
};

template <int Size>
class VecLexLessThan : public LexCompare<tcu::Vector<float, Size>, Size, std::less<float> >
{
};

enum TessPrimitiveType
{
	TESSPRIMITIVETYPE_TRIANGLES = 0,
	TESSPRIMITIVETYPE_QUADS,
	TESSPRIMITIVETYPE_ISOLINES,

	TESSPRIMITIVETYPE_LAST
};

enum SpacingMode
{
	SPACINGMODE_EQUAL,
	SPACINGMODE_FRACTIONAL_ODD,
	SPACINGMODE_FRACTIONAL_EVEN,

	SPACINGMODE_LAST
};

enum Winding
{
	WINDING_CCW = 0,
	WINDING_CW,

	WINDING_LAST
};

static inline const char* getTessPrimitiveTypeShaderName (TessPrimitiveType type)
{
	switch (type)
	{
		case TESSPRIMITIVETYPE_TRIANGLES:	return "triangles";
		case TESSPRIMITIVETYPE_QUADS:		return "quads";
		case TESSPRIMITIVETYPE_ISOLINES:	return "isolines";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

static inline const char* getSpacingModeShaderName (SpacingMode mode)
{
	switch (mode)
	{
		case SPACINGMODE_EQUAL:				return "equal_spacing";
		case SPACINGMODE_FRACTIONAL_ODD:	return "fractional_odd_spacing";
		case SPACINGMODE_FRACTIONAL_EVEN:	return "fractional_even_spacing";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

static inline const char* getWindingShaderName (Winding winding)
{
	switch (winding)
	{
		case WINDING_CCW:	return "ccw";
		case WINDING_CW:	return "cw";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode=false)
{
	return string() + "layout (" + getTessPrimitiveTypeShaderName(primType)
								 + ", " + getSpacingModeShaderName(spacing)
								 + ", " + getWindingShaderName(winding)
								 + (usePointMode ? ", point_mode" : "")
								 + ") in;\n";
}

static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, SpacingMode spacing, bool usePointMode=false)
{
	return string() + "layout (" + getTessPrimitiveTypeShaderName(primType)
								 + ", " + getSpacingModeShaderName(spacing)
								 + (usePointMode ? ", point_mode" : "")
								 + ") in;\n";
}

static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, Winding winding, bool usePointMode=false)
{
	return string() + "layout (" + getTessPrimitiveTypeShaderName(primType)
								 + ", " + getWindingShaderName(winding)
								 + (usePointMode ? ", point_mode" : "")
								 + ") in;\n";
}

static inline string getTessellationEvaluationInLayoutString (TessPrimitiveType primType, bool usePointMode=false)
{
	return string() + "layout (" + getTessPrimitiveTypeShaderName(primType)
								 + (usePointMode ? ", point_mode" : "")
								 + ") in;\n";
}

static inline deUint32 outputPrimitiveTypeGL (TessPrimitiveType tessPrimType, bool usePointMode)
{
	if (usePointMode)
		return GL_POINTS;
	else
	{
		switch (tessPrimType)
		{
			case TESSPRIMITIVETYPE_TRIANGLES:	return GL_TRIANGLES;
			case TESSPRIMITIVETYPE_QUADS:		return GL_TRIANGLES;
			case TESSPRIMITIVETYPE_ISOLINES:	return GL_LINES;
			default:
				DE_ASSERT(false);
				return (deUint32)-1;
		}
	}
}

static inline int numInnerTessellationLevels (TessPrimitiveType primType)
{
	switch (primType)
	{
		case TESSPRIMITIVETYPE_TRIANGLES:	return 1;
		case TESSPRIMITIVETYPE_QUADS:		return 2;
		case TESSPRIMITIVETYPE_ISOLINES:	return 0;
		default: DE_ASSERT(false); return -1;
	}
}

static inline int numOuterTessellationLevels (TessPrimitiveType primType)
{
	switch (primType)
	{
		case TESSPRIMITIVETYPE_TRIANGLES:	return 3;
		case TESSPRIMITIVETYPE_QUADS:		return 4;
		case TESSPRIMITIVETYPE_ISOLINES:	return 2;
		default: DE_ASSERT(false); return -1;
	}
}

static string tessellationLevelsString (const float* inner, int numInner, const float* outer, int numOuter)
{
	DE_ASSERT(numInner >= 0 && numOuter >= 0);
	return "inner: " + elemsStr(inner, inner+numInner) + ", outer: " + elemsStr(outer, outer+numOuter);
}

static string tessellationLevelsString (const float* inner, const float* outer, TessPrimitiveType primType)
{
	return tessellationLevelsString(inner, numInnerTessellationLevels(primType), outer, numOuterTessellationLevels(primType));
}

static string tessellationLevelsString (const float* inner, const float* outer)
{
	return tessellationLevelsString(inner, 2, outer, 4);
}

static inline float getClampedTessLevel (SpacingMode mode, float tessLevel)
{
	switch (mode)
	{
		case SPACINGMODE_EQUAL:				return de::max(1.0f, tessLevel);
		case SPACINGMODE_FRACTIONAL_ODD:	return de::max(1.0f, tessLevel);
		case SPACINGMODE_FRACTIONAL_EVEN:	return de::max(2.0f, tessLevel);
		default:
			DE_ASSERT(false);
			return -1.0f;
	}
}

static inline int getRoundedTessLevel (SpacingMode mode, float clampedTessLevel)
{
	int result = (int)deFloatCeil(clampedTessLevel);

	switch (mode)
	{
		case SPACINGMODE_EQUAL:											break;
		case SPACINGMODE_FRACTIONAL_ODD:	result += 1 - result % 2;	break;
		case SPACINGMODE_FRACTIONAL_EVEN:	result += result % 2;		break;
		default:
			DE_ASSERT(false);
	}
	DE_ASSERT(de::inRange<int>(result, 1, MINIMUM_MAX_TESS_GEN_LEVEL));

	return result;
}

static int getClampedRoundedTessLevel (SpacingMode mode, float tessLevel)
{
	return getRoundedTessLevel(mode, getClampedTessLevel(mode, tessLevel));
}

//! A description of an outer edge of a triangle, quad or isolines.
//! An outer edge can be described by the index of a u/v/w coordinate
//! and the coordinate's value along that edge.
struct OuterEdgeDescription
{
	int		constantCoordinateIndex;
	float	constantCoordinateValueChoices[2];
	int		numConstantCoordinateValueChoices;

	OuterEdgeDescription (int i, float c0)			: constantCoordinateIndex(i), numConstantCoordinateValueChoices(1) { constantCoordinateValueChoices[0] = c0; }
	OuterEdgeDescription (int i, float c0, float c1)	: constantCoordinateIndex(i), numConstantCoordinateValueChoices(2) { constantCoordinateValueChoices[0] = c0; constantCoordinateValueChoices[1] = c1; }

	string description (void) const
	{
		static const char* const	coordinateNames[] = { "u", "v", "w" };
		string						result;
		for (int i = 0; i < numConstantCoordinateValueChoices; i++)
			result += string() + (i > 0 ? " or " : "") + coordinateNames[constantCoordinateIndex] + "=" + de::toString(constantCoordinateValueChoices[i]);
		return result;
	}

	bool contains (const Vec3& v) const
	{
		for (int i = 0; i < numConstantCoordinateValueChoices; i++)
			if (v[constantCoordinateIndex] == constantCoordinateValueChoices[i])
				return true;
		return false;
	}
};

static vector<OuterEdgeDescription> outerEdgeDescriptions (TessPrimitiveType primType)
{
	static const OuterEdgeDescription triangleOuterEdgeDescriptions[3] =
	{
		OuterEdgeDescription(0, 0.0f),
		OuterEdgeDescription(1, 0.0f),
		OuterEdgeDescription(2, 0.0f)
	};

	static const OuterEdgeDescription quadOuterEdgeDescriptions[4] =
	{
		OuterEdgeDescription(0, 0.0f),
		OuterEdgeDescription(1, 0.0f),
		OuterEdgeDescription(0, 1.0f),
		OuterEdgeDescription(1, 1.0f)
	};

	static const OuterEdgeDescription isolinesOuterEdgeDescriptions[1] =
	{
		OuterEdgeDescription(0, 0.0f, 1.0f),
	};

	switch (primType)
	{
		case TESSPRIMITIVETYPE_TRIANGLES:	return arrayToVector(triangleOuterEdgeDescriptions);
		case TESSPRIMITIVETYPE_QUADS:		return arrayToVector(quadOuterEdgeDescriptions);
		case TESSPRIMITIVETYPE_ISOLINES:	return arrayToVector(isolinesOuterEdgeDescriptions);
		default: DE_ASSERT(false); return vector<OuterEdgeDescription>();
	}
}

// \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that u+v+w == 1.0f, or [uvw] + (1.0f-[uvw]) == 1.0f).
static vector<Vec3> generateReferenceTriangleTessCoords (SpacingMode spacingMode, int inner, int outer0, int outer1, int outer2)
{
	vector<Vec3> tessCoords;

	if (inner == 1)
	{
		if (outer0 == 1 && outer1 == 1 && outer2 == 1)
		{
			tessCoords.push_back(Vec3(1.0f, 0.0f, 0.0f));
			tessCoords.push_back(Vec3(0.0f, 1.0f, 0.0f));
			tessCoords.push_back(Vec3(0.0f, 0.0f, 1.0f));
			return tessCoords;
		}
		else
			return generateReferenceTriangleTessCoords(spacingMode, spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
																	outer0, outer1, outer2);
	}
	else
	{
		for (int i = 0; i < outer0; i++) { const float v = (float)i / (float)outer0; tessCoords.push_back(Vec3(	   0.0f,		   v,	1.0f - v)); }
		for (int i = 0; i < outer1; i++) { const float v = (float)i / (float)outer1; tessCoords.push_back(Vec3(1.0f - v,		0.0f,		   v)); }
		for (int i = 0; i < outer2; i++) { const float v = (float)i / (float)outer2; tessCoords.push_back(Vec3(		  v,	1.0f - v,		0.0f)); }

		const int numInnerTriangles = inner/2;
		for (int innerTriangleNdx = 0; innerTriangleNdx < numInnerTriangles; innerTriangleNdx++)
		{
			const int curInnerTriangleLevel = inner - 2*(innerTriangleNdx+1);

			if (curInnerTriangleLevel == 0)
				tessCoords.push_back(Vec3(1.0f/3.0f));
			else
			{
				const float		minUVW		= (float)(2 * (innerTriangleNdx + 1)) / (float)(3 * inner);
				const float		maxUVW		= 1.0f - 2.0f*minUVW;
				const Vec3		corners[3]	=
				{
					Vec3(maxUVW, minUVW, minUVW),
					Vec3(minUVW, maxUVW, minUVW),
					Vec3(minUVW, minUVW, maxUVW)
				};

				for (int i = 0; i < curInnerTriangleLevel; i++)
				{
					const float f = (float)i / (float)curInnerTriangleLevel;
					for (int j = 0; j < 3; j++)
						tessCoords.push_back((1.0f - f)*corners[j] + f*corners[(j+1)%3]);
				}
			}
		}

		return tessCoords;
	}
}

static int referenceTriangleNonPointModePrimitiveCount (SpacingMode spacingMode, int inner, int outer0, int outer1, int outer2)
{
	if (inner == 1)
	{
		if (outer0 == 1 && outer1 == 1 && outer2 == 1)
			return 1;
		else
			return referenceTriangleNonPointModePrimitiveCount(spacingMode, spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
																			outer0, outer1, outer2);
	}
	else
	{
		int result = outer0 + outer1 + outer2;

		const int numInnerTriangles = inner/2;
		for (int innerTriangleNdx = 0; innerTriangleNdx < numInnerTriangles; innerTriangleNdx++)
		{
			const int curInnerTriangleLevel = inner - 2*(innerTriangleNdx+1);

			if (curInnerTriangleLevel == 1)
				result += 4;
			else
				result += 2*3*curInnerTriangleLevel;
		}

		return result;
	}
}

// \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that [uv] + (1.0f-[uv]) == 1.0f).
static vector<Vec3> generateReferenceQuadTessCoords (SpacingMode spacingMode, int inner0, int inner1, int outer0, int outer1, int outer2, int outer3)
{
	vector<Vec3> tessCoords;

	if (inner0 == 1 || inner1 == 1)
	{
		if (inner0 == 1 && inner1 == 1 && outer0 == 1 && outer1 == 1 && outer2 == 1 && outer3 == 1)
		{
			tessCoords.push_back(Vec3(0.0f, 0.0f, 0.0f));
			tessCoords.push_back(Vec3(1.0f, 0.0f, 0.0f));
			tessCoords.push_back(Vec3(0.0f, 1.0f, 0.0f));
			tessCoords.push_back(Vec3(1.0f, 1.0f, 0.0f));
			return tessCoords;
		}
		else
			return generateReferenceQuadTessCoords(spacingMode, inner0 > 1 ? inner0 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
																inner1 > 1 ? inner1 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
																outer0, outer1, outer2, outer3);
	}
	else
	{
		for (int i = 0; i < outer0; i++) { const float v = (float)i / (float)outer0; tessCoords.push_back(Vec3(0.0f,	v,			0.0f)); }
		for (int i = 0; i < outer1; i++) { const float v = (float)i / (float)outer1; tessCoords.push_back(Vec3(1.0f-v,	0.0f,		0.0f)); }
		for (int i = 0; i < outer2; i++) { const float v = (float)i / (float)outer2; tessCoords.push_back(Vec3(1.0f,	1.0f-v,		0.0f)); }
		for (int i = 0; i < outer3; i++) { const float v = (float)i / (float)outer3; tessCoords.push_back(Vec3(v,		1.0f,		0.0f)); }

		for (int innerVtxY = 0; innerVtxY < inner1-1; innerVtxY++)
		for (int innerVtxX = 0; innerVtxX < inner0-1; innerVtxX++)
			tessCoords.push_back(Vec3((float)(innerVtxX + 1) / (float)inner0,
									  (float)(innerVtxY + 1) / (float)inner1,
									  0.0f));

		return tessCoords;
	}
}

static int referenceQuadNonPointModePrimitiveCount (SpacingMode spacingMode, int inner0, int inner1, int outer0, int outer1, int outer2, int outer3)
{
	vector<Vec3> tessCoords;

	if (inner0 == 1 || inner1 == 1)
	{
		if (inner0 == 1 && inner1 == 1 && outer0 == 1 && outer1 == 1 && outer2 == 1 && outer3 == 1)
			return 2;
		else
			return referenceQuadNonPointModePrimitiveCount(spacingMode, inner0 > 1 ? inner0 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
																		inner1 > 1 ? inner1 : spacingMode == SPACINGMODE_FRACTIONAL_ODD ? 3 : 2,
																		outer0, outer1, outer2, outer3);
	}
	else
		return 2*(inner0-2)*(inner1-2) + 2*(inner0-2) + 2*(inner1-2) + outer0+outer1+outer2+outer3;
}

// \note The tessellation coordinates generated by this function could break some of the rules given in the spec (e.g. it may not exactly hold that [uv] + (1.0f-[uv]) == 1.0f).
static vector<Vec3> generateReferenceIsolineTessCoords (int outer0, int outer1)
{
	vector<Vec3> tessCoords;

	for (int y = 0; y < outer0;		y++)
	for (int x = 0; x < outer1+1;	x++)
		tessCoords.push_back(Vec3((float)x / (float)outer1,
												  (float)y / (float)outer0,
												  0.0f));

	return tessCoords;
}

static int referenceIsolineNonPointModePrimitiveCount (int outer0, int outer1)
{
	return outer0*outer1;
}

static void getClampedRoundedTriangleTessLevels (SpacingMode spacingMode, const float* innerSrc, const float* outerSrc, int* innerDst, int *outerDst)
{
	innerDst[0] = getClampedRoundedTessLevel(spacingMode, innerSrc[0]);
	for (int i = 0; i < 3; i++)
		outerDst[i] = getClampedRoundedTessLevel(spacingMode, outerSrc[i]);
}

static void getClampedRoundedQuadTessLevels (SpacingMode spacingMode, const float* innerSrc, const float* outerSrc, int* innerDst, int *outerDst)
{
	for (int i = 0; i < 2; i++)
		innerDst[i] = getClampedRoundedTessLevel(spacingMode, innerSrc[i]);
	for (int i = 0; i < 4; i++)
		outerDst[i] = getClampedRoundedTessLevel(spacingMode, outerSrc[i]);
}

static void getClampedRoundedIsolineTessLevels (SpacingMode spacingMode, const float* outerSrc, int* outerDst)
{
	outerDst[0] = getClampedRoundedTessLevel(SPACINGMODE_EQUAL,	outerSrc[0]);
	outerDst[1] = getClampedRoundedTessLevel(spacingMode,		outerSrc[1]);
}

static inline bool isPatchDiscarded (TessPrimitiveType primitiveType, const float* outerLevels)
{
	const int numOuterLevels = numOuterTessellationLevels(primitiveType);
	for (int i = 0; i < numOuterLevels; i++)
		if (outerLevels[i] <= 0.0f)
			return true;
	return false;
}

static vector<Vec3> generateReferenceTessCoords (TessPrimitiveType primitiveType, SpacingMode spacingMode, const float* innerLevels, const float* outerLevels)
{
	if (isPatchDiscarded(primitiveType, outerLevels))
		return vector<Vec3>();

	switch (primitiveType)
	{
		case TESSPRIMITIVETYPE_TRIANGLES:
		{
			int inner;
			int outer[3];
			getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]);

			if (spacingMode != SPACINGMODE_EQUAL)
			{
				// \note For fractional spacing modes, exact results are implementation-defined except in special cases.
				DE_ASSERT(de::abs(innerLevels[0] - (float)inner) < 0.001f);
				for (int i = 0; i < 3; i++)
					DE_ASSERT(de::abs(outerLevels[i] - (float)outer[i]) < 0.001f);
				DE_ASSERT(inner > 1 || (outer[0] == 1 && outer[1] == 1 && outer[2] == 1));
			}

			return generateReferenceTriangleTessCoords(spacingMode, inner, outer[0], outer[1], outer[2]);
		}

		case TESSPRIMITIVETYPE_QUADS:
		{
			int inner[2];
			int outer[4];
			getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]);

			if (spacingMode != SPACINGMODE_EQUAL)
			{
				// \note For fractional spacing modes, exact results are implementation-defined except in special cases.
				for (int i = 0; i < 2; i++)
					DE_ASSERT(de::abs(innerLevels[i] - (float)inner[i]) < 0.001f);
				for (int i = 0; i < 4; i++)
					DE_ASSERT(de::abs(outerLevels[i] - (float)outer[i]) < 0.001f);

				DE_ASSERT((inner[0] > 1 && inner[1] > 1) || (inner[0] == 1 && inner[1] == 1 && outer[0] == 1 && outer[1] == 1 && outer[2] == 1 && outer[3] == 1));
			}

			return generateReferenceQuadTessCoords(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2], outer[3]);
		}

		case TESSPRIMITIVETYPE_ISOLINES:
		{
			int outer[2];
			getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]);

			if (spacingMode != SPACINGMODE_EQUAL)
			{
				// \note For fractional spacing modes, exact results are implementation-defined except in special cases.
				DE_ASSERT(de::abs(outerLevels[1] - (float)outer[1]) < 0.001f);
			}

			return generateReferenceIsolineTessCoords(outer[0], outer[1]);
		}

		default:
			DE_ASSERT(false);
			return vector<Vec3>();
	}
}

static int referencePointModePrimitiveCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, const float* innerLevels, const float* outerLevels)
{
	if (isPatchDiscarded(primitiveType, outerLevels))
		return 0;

	switch (primitiveType)
	{
		case TESSPRIMITIVETYPE_TRIANGLES:
		{
			int inner;
			int outer[3];
			getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]);
			return (int)generateReferenceTriangleTessCoords(spacingMode, inner, outer[0], outer[1], outer[2]).size();
		}

		case TESSPRIMITIVETYPE_QUADS:
		{
			int inner[2];
			int outer[4];
			getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]);
			return (int)generateReferenceQuadTessCoords(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2], outer[3]).size();
		}

		case TESSPRIMITIVETYPE_ISOLINES:
		{
			int outer[2];
			getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]);
			return (int)generateReferenceIsolineTessCoords(outer[0], outer[1]).size();
		}

		default:
			DE_ASSERT(false);
			return -1;
	}
}

static int referenceNonPointModePrimitiveCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, const float* innerLevels, const float* outerLevels)
{
	if (isPatchDiscarded(primitiveType, outerLevels))
		return 0;

	switch (primitiveType)
	{
		case TESSPRIMITIVETYPE_TRIANGLES:
		{
			int inner;
			int outer[3];
			getClampedRoundedTriangleTessLevels(spacingMode, innerLevels, outerLevels, &inner, &outer[0]);
			return referenceTriangleNonPointModePrimitiveCount(spacingMode, inner, outer[0], outer[1], outer[2]);
		}

		case TESSPRIMITIVETYPE_QUADS:
		{
			int inner[2];
			int outer[4];
			getClampedRoundedQuadTessLevels(spacingMode, innerLevels, outerLevels, &inner[0], &outer[0]);
			return referenceQuadNonPointModePrimitiveCount(spacingMode, inner[0], inner[1], outer[0], outer[1], outer[2], outer[3]);
		}

		case TESSPRIMITIVETYPE_ISOLINES:
		{
			int outer[2];
			getClampedRoundedIsolineTessLevels(spacingMode, &outerLevels[0], &outer[0]);
			return referenceIsolineNonPointModePrimitiveCount(outer[0], outer[1]);
		}

		default:
			DE_ASSERT(false);
			return -1;
	}
}

static int referencePrimitiveCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, bool usePointMode, const float* innerLevels, const float* outerLevels)
{
	return usePointMode ? referencePointModePrimitiveCount		(primitiveType, spacingMode, innerLevels, outerLevels)
						: referenceNonPointModePrimitiveCount	(primitiveType, spacingMode, innerLevels, outerLevels);
}

static int referenceVertexCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, bool usePointMode, const float* innerLevels, const float* outerLevels)
{
	return referencePrimitiveCount(primitiveType, spacingMode, usePointMode, innerLevels, outerLevels)
		   * numVerticesPerPrimitive(outputPrimitiveTypeGL(primitiveType, usePointMode));
}

//! Helper for calling referenceVertexCount multiple times with different tessellation levels.
//! \note Levels contains inner and outer levels, per patch, in order IIOOOO. The full 6 levels must always be present, irrespective of primitiveType.
static int multiplePatchReferenceVertexCount (TessPrimitiveType primitiveType, SpacingMode spacingMode, bool usePointMode, const float* levels, int numPatches)
{
	int result = 0;
	for (int patchNdx = 0; patchNdx < numPatches; patchNdx++)
		result += referenceVertexCount(primitiveType, spacingMode, usePointMode, &levels[6*patchNdx + 0], &levels[6*patchNdx + 2]);
	return result;
}

vector<float> generateRandomPatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel, de::Random& rnd)
{
	vector<float> tessLevels(numPatches*6);

	for (int patchNdx = 0; patchNdx < numPatches; patchNdx++)
	{
		float* const inner = &tessLevels[patchNdx*6 + 0];
		float* const outer = &tessLevels[patchNdx*6 + 2];

		for (int j = 0; j < 2; j++)
			inner[j] = rnd.getFloat(1.0f, 62.0f);
		for (int j = 0; j < 4; j++)
			outer[j] = j == constantOuterLevelIndex ? constantOuterLevel : rnd.getFloat(1.0f, 62.0f);
	}

	return tessLevels;
}

static inline void drawPoint (tcu::Surface& dst, int centerX, int centerY, const tcu::RGBA& color, int size)
{
	const int width		= dst.getWidth();
	const int height	= dst.getHeight();
	DE_ASSERT(de::inBounds(centerX, 0, width) && de::inBounds(centerY, 0, height));
	DE_ASSERT(size > 0);

	for (int yOff = -((size-1)/2); yOff <= size/2; yOff++)
	for (int xOff = -((size-1)/2); xOff <= size/2; xOff++)
	{
		const int pixX = centerX + xOff;
		const int pixY = centerY + yOff;
		if (de::inBounds(pixX, 0, width) && de::inBounds(pixY, 0, height))
			dst.setPixel(pixX, pixY, color);
	}
}

static void drawTessCoordPoint (tcu::Surface& dst, TessPrimitiveType primitiveType, const Vec3& pt, const tcu::RGBA& color, int size)
{
	// \note These coordinates should match the description in the log message in TessCoordCase::iterate.

	static const Vec2 triangleCorners[3] =
	{
		Vec2(0.95f, 0.95f),
		Vec2(0.5f,  0.95f - 0.9f*deFloatSqrt(3.0f/4.0f)),
		Vec2(0.05f, 0.95f)
	};

	static const float quadIsolineLDRU[4] =
	{
		0.1f, 0.9f, 0.9f, 0.1f
	};

	const Vec2 dstPos = primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? pt.x()*triangleCorners[0]
																	 + pt.y()*triangleCorners[1]
																	 + pt.z()*triangleCorners[2]

					  : primitiveType == TESSPRIMITIVETYPE_QUADS ||
						primitiveType == TESSPRIMITIVETYPE_ISOLINES ? Vec2((1.0f - pt.x())*quadIsolineLDRU[0] + pt.x()*quadIsolineLDRU[2],
																		   (1.0f - pt.y())*quadIsolineLDRU[1] + pt.y()*quadIsolineLDRU[3])

					  : Vec2(-1.0f);

	drawPoint(dst, (int)(dstPos.x() * (float)dst.getWidth()), (int)(dstPos.y() * (float)dst.getHeight()), color, size);
}

static void drawTessCoordVisualization (tcu::Surface& dst, TessPrimitiveType primitiveType, const vector<Vec3>& coords)
{
	const int		imageWidth		= 256;
	const int		imageHeight		= 256;
	dst.setSize(imageWidth, imageHeight);

	tcu::clear(dst.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));

	for (int i = 0; i < (int)coords.size(); i++)
		drawTessCoordPoint(dst, primitiveType, coords[i], tcu::RGBA::white(), 2);
}

static int binarySearchFirstVec3WithXAtLeast (const vector<Vec3>& sorted, float x)
{
	const Vec3 ref(x, 0.0f, 0.0f);
	const vector<Vec3>::const_iterator first = std::lower_bound(sorted.begin(), sorted.end(), ref, vec3XLessThan);
	if (first == sorted.end())
		return -1;
	return (int)std::distance(sorted.begin(), first);
}

template <typename T, typename P>
static vector<T> sorted (const vector<T>& unsorted, P pred)
{
	vector<T> result = unsorted;
	std::sort(result.begin(), result.end(), pred);
	return result;
}

template <typename T>
static vector<T> sorted (const vector<T>& unsorted)
{
	vector<T> result = unsorted;
	std::sort(result.begin(), result.end());
	return result;
}

// Check that all points in subset are (approximately) present also in superset.
static bool oneWayComparePointSets (TestLog&				log,
									tcu::Surface&			errorDst,
									TessPrimitiveType		primitiveType,
									const vector<Vec3>&		subset,
									const vector<Vec3>&		superset,
									const char*				subsetName,
									const char*				supersetName,
									const tcu::RGBA&		errorColor)
{
	const vector<Vec3>	supersetSorted			= sorted(superset, vec3XLessThan);
	const float			epsilon					= 0.01f;
	const int			maxNumFailurePrints		= 5;
	int					numFailuresDetected		= 0;

	for (int subNdx = 0; subNdx < (int)subset.size(); subNdx++)
	{
		const Vec3& subPt = subset[subNdx];

		bool matchFound = false;

		{
			// Binary search the index of the first point in supersetSorted with x in the [subPt.x() - epsilon, subPt.x() + epsilon] range.
			const Vec3	matchMin			= subPt - epsilon;
			const Vec3	matchMax			= subPt + epsilon;
			int			firstCandidateNdx	= binarySearchFirstVec3WithXAtLeast(supersetSorted, matchMin.x());

			if (firstCandidateNdx >= 0)
			{
				// Compare subPt to all points in supersetSorted with x in the [subPt.x() - epsilon, subPt.x() + epsilon] range.
				for (int superNdx = firstCandidateNdx; superNdx < (int)supersetSorted.size() && supersetSorted[superNdx].x() <= matchMax.x(); superNdx++)
				{
					const Vec3& superPt = supersetSorted[superNdx];

					if (tcu::boolAll(tcu::greaterThanEqual	(superPt, matchMin)) &&
						tcu::boolAll(tcu::lessThanEqual		(superPt, matchMax)))
					{
						matchFound = true;
						break;
					}
				}
			}
		}

		if (!matchFound)
		{
			numFailuresDetected++;
			if (numFailuresDetected < maxNumFailurePrints)
				log << TestLog::Message << "Failure: no matching " << supersetName << " point found for " << subsetName << " point " << subPt << TestLog::EndMessage;
			else if (numFailuresDetected == maxNumFailurePrints)
				log << TestLog::Message << "Note: More errors follow" << TestLog::EndMessage;

			drawTessCoordPoint(errorDst, primitiveType, subPt, errorColor, 4);
		}
	}

	return numFailuresDetected == 0;
}

static bool compareTessCoords (TestLog& log, TessPrimitiveType primitiveType, const vector<Vec3>& refCoords, const vector<Vec3>& resCoords)
{
	tcu::Surface	refVisual;
	tcu::Surface	resVisual;
	bool			success = true;

	drawTessCoordVisualization(refVisual, primitiveType, refCoords);
	drawTessCoordVisualization(resVisual, primitiveType, resCoords);

	// Check that all points in reference also exist in result.
	success = oneWayComparePointSets(log, refVisual, primitiveType, refCoords, resCoords, "reference", "result", tcu::RGBA::blue()) && success;
	// Check that all points in result also exist in reference.
	success = oneWayComparePointSets(log, resVisual, primitiveType, resCoords, refCoords, "result", "reference", tcu::RGBA::red()) && success;

	if (!success)
	{
		log << TestLog::Message << "Note: in the following reference visualization, points that are missing in result point set are blue (if any)" << TestLog::EndMessage
			<< TestLog::Image("RefTessCoordVisualization", "Reference tessCoord visualization", refVisual)
			<< TestLog::Message << "Note: in the following result visualization, points that are missing in reference point set are red (if any)" << TestLog::EndMessage;
	}

	log << TestLog::Image("ResTessCoordVisualization", "Result tessCoord visualization", resVisual);

	return success;
}

namespace VerifyFractionalSpacingSingleInternal
{

struct Segment
{
	int		index; //!< Index of left coordinate in sortedXCoords.
	float	length;
	Segment (void)						: index(-1),		length(-1.0f)	{}
	Segment (int index_, float length_)	: index(index_),	length(length_)	{}

	static vector<float> lengths (const vector<Segment>& segments) { return members(segments, &Segment::length); }
};

}

/*--------------------------------------------------------------------*//*!
 * \brief Verify fractional spacing conditions for a single line
 *
 * Verify that the splitting of an edge (resulting from e.g. an isoline
 * with outer levels { 1.0, tessLevel }) with a given fractional spacing
 * mode fulfills certain conditions given in the spec.
 *
 * Note that some conditions can't be checked from just one line
 * (specifically, that the additional segment decreases monotonically
 * length and the requirement that the additional segments be placed
 * identically for identical values of clamped level).
 *
 * Therefore, the function stores some values to additionalSegmentLengthDst
 * and additionalSegmentLocationDst that can later be given to
 * verifyFractionalSpacingMultiple(). A negative value in length means that
 * no additional segments are present, i.e. there's just one segment.
 * A negative value in location means that the value wasn't determinable,
 * i.e. all segments had same length.
 * The values are not stored if false is returned.
 *//*--------------------------------------------------------------------*/
static bool verifyFractionalSpacingSingle (TestLog& log, SpacingMode spacingMode, float tessLevel, const vector<float>& coords, float& additionalSegmentLengthDst, int& additionalSegmentLocationDst)
{
	using namespace VerifyFractionalSpacingSingleInternal;

	DE_ASSERT(spacingMode == SPACINGMODE_FRACTIONAL_ODD || spacingMode == SPACINGMODE_FRACTIONAL_EVEN);

	const float				clampedLevel	= getClampedTessLevel(spacingMode, tessLevel);
	const int				finalLevel		= getRoundedTessLevel(spacingMode, clampedLevel);
	const vector<float>		sortedCoords	= sorted(coords);
	string					failNote		= "Note: tessellation level is " + de::toString(tessLevel) + "\nNote: sorted coordinates are:\n    " + containerStr(sortedCoords);

	if ((int)coords.size() != finalLevel + 1)
	{
		log << TestLog::Message << "Failure: number of vertices is " << coords.size() << "; expected " << finalLevel + 1
			<< " (clamped tessellation level is " << clampedLevel << ")"
			<< "; final level (clamped level rounded up to " << (spacingMode == SPACINGMODE_FRACTIONAL_EVEN ? "even" : "odd") << ") is " << finalLevel
			<< " and should equal the number of segments, i.e. number of vertices minus 1" << TestLog::EndMessage
			<< TestLog::Message << failNote << TestLog::EndMessage;
		return false;
	}

	if (sortedCoords[0] != 0.0f || sortedCoords.back() != 1.0f)
	{
		log << TestLog::Message << "Failure: smallest coordinate should be 0.0 and biggest should be 1.0" << TestLog::EndMessage
			<< TestLog::Message << failNote << TestLog::EndMessage;
		return false;
	}

	{
		vector<Segment> segments(finalLevel);
		for (int i = 0; i < finalLevel; i++)
			segments[i] = Segment(i, sortedCoords[i+1] - sortedCoords[i]);

		failNote += "\nNote: segment lengths are, from left to right:\n    " + containerStr(Segment::lengths(segments));

		{
			// Divide segments to two different groups based on length.

			vector<Segment> segmentsA;
			vector<Segment> segmentsB;
			segmentsA.push_back(segments[0]);

			for (int segNdx = 1; segNdx < (int)segments.size(); segNdx++)
			{
				const float		epsilon		= 0.001f;
				const Segment&	seg			= segments[segNdx];

				if (de::abs(seg.length - segmentsA[0].length) < epsilon)
					segmentsA.push_back(seg);
				else if (segmentsB.empty() || de::abs(seg.length - segmentsB[0].length) < epsilon)
					segmentsB.push_back(seg);
				else
				{
					log << TestLog::Message << "Failure: couldn't divide segments to 2 groups by length; "
											<< "e.g. segment of length " << seg.length << " isn't approximately equal to either "
											<< segmentsA[0].length << " or " << segmentsB[0].length << TestLog::EndMessage
											<< TestLog::Message << failNote << TestLog::EndMessage;
					return false;
				}
			}

			if (clampedLevel == (float)finalLevel)
			{
				// All segments should be of equal length.
				if (!segmentsA.empty() && !segmentsB.empty())
				{
					log << TestLog::Message << "Failure: clamped and final tessellation level are equal, but not all segments are of equal length." << TestLog::EndMessage
						<< TestLog::Message << failNote << TestLog::EndMessage;
					return false;
				}
			}

			if (segmentsA.empty() || segmentsB.empty()) // All segments have same length. This is ok.
			{
				additionalSegmentLengthDst		= segments.size() == 1 ? -1.0f : segments[0].length;
				additionalSegmentLocationDst	= -1;
				return true;
			}

			if (segmentsA.size() != 2 && segmentsB.size() != 2)
			{
				log << TestLog::Message << "Failure: when dividing the segments to 2 groups by length, neither of the two groups has exactly 2 or 0 segments in it" << TestLog::EndMessage
					<< TestLog::Message << failNote << TestLog::EndMessage;
				return false;
			}

			// For convenience, arrange so that the 2-segment group is segmentsB.
			if (segmentsB.size() != 2)
				std::swap(segmentsA, segmentsB);

			// \note For 4-segment lines both segmentsA and segmentsB have 2 segments each.
			//		 Thus, we can't be sure which ones were meant as the additional segments.
			//		 We give the benefit of the doubt by assuming that they're the shorter
			//		 ones (as they should).

			if (segmentsA.size() != 2)
			{
				if (segmentsB[0].length > segmentsA[0].length + 0.001f)
				{
					log << TestLog::Message << "Failure: the two additional segments are longer than the other segments" << TestLog::EndMessage
						<< TestLog::Message << failNote << TestLog::EndMessage;
					return false;
				}
			}
			else
			{
				// We have 2 segmentsA and 2 segmentsB, ensure segmentsB has the shorter lengths
				if (segmentsB[0].length > segmentsA[0].length)
					std::swap(segmentsA, segmentsB);
			}

			// Check that the additional segments are placed symmetrically.
			if (segmentsB[0].index + segmentsB[1].index + 1 != (int)segments.size())
			{
				log << TestLog::Message << "Failure: the two additional segments aren't placed symmetrically; "
										<< "one is at index " << segmentsB[0].index << " and other is at index " << segmentsB[1].index
										<< " (note: the two indexes should sum to " << (int)segments.size()-1 << ", i.e. numberOfSegments-1)" << TestLog::EndMessage
					<< TestLog::Message << failNote << TestLog::EndMessage;
				return false;
			}

			additionalSegmentLengthDst = segmentsB[0].length;
			if (segmentsA.size() != 2)
				additionalSegmentLocationDst = de::min(segmentsB[0].index, segmentsB[1].index);
			else
				additionalSegmentLocationDst = segmentsB[0].length < segmentsA[0].length - 0.001f ? de::min(segmentsB[0].index, segmentsB[1].index)
											 : -1; // \note -1 when can't reliably decide which ones are the additional segments, a or b.

			return true;
		}
	}
}

namespace VerifyFractionalSpacingMultipleInternal
{

struct LineData
{
	float	tessLevel;
	float	additionalSegmentLength;
	int		additionalSegmentLocation;
	LineData (float lev, float len, int loc) : tessLevel(lev), additionalSegmentLength(len), additionalSegmentLocation(loc) {}
};

}

/*--------------------------------------------------------------------*//*!
 * \brief Verify fractional spacing conditions between multiple lines
 *
 * Verify the fractional spacing conditions that are not checked in
 * verifyFractionalSpacingSingle(). Uses values given by said function
 * as parameters, in addition to the spacing mode and tessellation level.
 *//*--------------------------------------------------------------------*/
static bool verifyFractionalSpacingMultiple (TestLog& log, SpacingMode spacingMode, const vector<float>& tessLevels, const vector<float>& additionalSegmentLengths, const vector<int>& additionalSegmentLocations)
{
	using namespace VerifyFractionalSpacingMultipleInternal;

	DE_ASSERT(spacingMode == SPACINGMODE_FRACTIONAL_ODD || spacingMode == SPACINGMODE_FRACTIONAL_EVEN);
	DE_ASSERT(tessLevels.size() == additionalSegmentLengths.size() &&
			  tessLevels.size() == additionalSegmentLocations.size());

	vector<LineData> lineDatas;

	for (int i = 0; i < (int)tessLevels.size(); i++)
		lineDatas.push_back(LineData(tessLevels[i], additionalSegmentLengths[i], additionalSegmentLocations[i]));

	{
		const vector<LineData> lineDatasSortedByLevel = sorted(lineDatas, memberPred<std::less>(&LineData::tessLevel));

		// Check that lines with identical clamped tessellation levels have identical additionalSegmentLocation.

		for (int lineNdx = 1; lineNdx < (int)lineDatasSortedByLevel.size(); lineNdx++)
		{
			const LineData& curData		= lineDatasSortedByLevel[lineNdx];
			const LineData& prevData	= lineDatasSortedByLevel[lineNdx-1];

			if (curData.additionalSegmentLocation < 0 || prevData.additionalSegmentLocation < 0)
				continue; // Unknown locations, skip.

			if (getClampedTessLevel(spacingMode, curData.tessLevel) == getClampedTessLevel(spacingMode, prevData.tessLevel) &&
				curData.additionalSegmentLocation != prevData.additionalSegmentLocation)
			{
				log << TestLog::Message << "Failure: additional segments not located identically for two edges with identical clamped tessellation levels" << TestLog::EndMessage
					<< TestLog::Message << "Note: tessellation levels are " << curData.tessLevel << " and " << prevData.tessLevel
										<< " (clamped level " << getClampedTessLevel(spacingMode, curData.tessLevel) << ")"
										<< "; but first additional segments located at indices "
										<< curData.additionalSegmentLocation << " and " << prevData.additionalSegmentLocation << ", respectively" << TestLog::EndMessage;
				return false;
			}
		}

		// Check that, among lines with same clamped rounded tessellation level, additionalSegmentLength is monotonically decreasing with "clampedRoundedTessLevel - clampedTessLevel" (the "fraction").

		for (int lineNdx = 1; lineNdx < (int)lineDatasSortedByLevel.size(); lineNdx++)
		{
			const LineData&		curData				= lineDatasSortedByLevel[lineNdx];
			const LineData&		prevData			= lineDatasSortedByLevel[lineNdx-1];

			if (curData.additionalSegmentLength < 0.0f || prevData.additionalSegmentLength < 0.0f)
				continue; // Unknown segment lengths, skip.

			const float			curClampedLevel		= getClampedTessLevel(spacingMode, curData.tessLevel);
			const float			prevClampedLevel	= getClampedTessLevel(spacingMode, prevData.tessLevel);
			const int			curFinalLevel		= getRoundedTessLevel(spacingMode, curClampedLevel);
			const int			prevFinalLevel		= getRoundedTessLevel(spacingMode, prevClampedLevel);

			if (curFinalLevel != prevFinalLevel)
				continue;

			const float			curFraction		= (float)curFinalLevel - curClampedLevel;
			const float			prevFraction	= (float)prevFinalLevel - prevClampedLevel;

			if (curData.additionalSegmentLength < prevData.additionalSegmentLength ||
				(curClampedLevel == prevClampedLevel && curData.additionalSegmentLength != prevData.additionalSegmentLength))
			{
				log << TestLog::Message << "Failure: additional segment length isn't monotonically decreasing with the fraction <n> - <f>, among edges with same final tessellation level" << TestLog::EndMessage
					<< TestLog::Message << "Note: <f> stands for the clamped tessellation level and <n> for the final (rounded and clamped) tessellation level" << TestLog::EndMessage
					<< TestLog::Message << "Note: two edges have tessellation levels " << prevData.tessLevel << " and " << curData.tessLevel << " respectively"
										<< ", clamped " << prevClampedLevel << " and " << curClampedLevel << ", final " << prevFinalLevel << " and " << curFinalLevel
										<< "; fractions are " << prevFraction << " and " << curFraction
										<< ", but resulted in segment lengths " << prevData.additionalSegmentLength << " and " << curData.additionalSegmentLength << TestLog::EndMessage;
				return false;
			}
		}
	}

	return true;
}

//! Compare triangle sets, ignoring triangle order and vertex order within triangle, and possibly exclude some triangles too.
template <typename IsTriangleRelevantT>
static bool compareTriangleSets (const vector<Vec3>&			coordsA,
								 const vector<Vec3>&			coordsB,
								 TestLog&						log,
								 const IsTriangleRelevantT&		isTriangleRelevant,
								 const char*					ignoredTriangleDescription = DE_NULL)
{
	typedef tcu::Vector<Vec3, 3>							Triangle;
	typedef LexCompare<Triangle, 3, VecLexLessThan<3> >		TriangleLexLessThan;
	typedef std::set<Triangle, TriangleLexLessThan>			TriangleSet;

	DE_ASSERT(coordsA.size() % 3 == 0 && coordsB.size() % 3 == 0);

	const int		numTrianglesA = (int)coordsA.size()/3;
	const int		numTrianglesB = (int)coordsB.size()/3;
	TriangleSet		trianglesA;
	TriangleSet		trianglesB;

	for (int aOrB = 0; aOrB < 2; aOrB++)
	{
		const vector<Vec3>&		coords			= aOrB == 0 ? coordsA			: coordsB;
		const int				numTriangles	= aOrB == 0 ? numTrianglesA		: numTrianglesB;
		TriangleSet&			triangles		= aOrB == 0 ? trianglesA		: trianglesB;

		for (int triNdx = 0; triNdx < numTriangles; triNdx++)
		{
			Triangle triangle(coords[3*triNdx + 0],
							  coords[3*triNdx + 1],
							  coords[3*triNdx + 2]);

			if (isTriangleRelevant(triangle.getPtr()))
			{
				std::sort(triangle.getPtr(), triangle.getPtr()+3, VecLexLessThan<3>());
				triangles.insert(triangle);
			}
		}
	}

	{
		TriangleSet::const_iterator aIt = trianglesA.begin();
		TriangleSet::const_iterator bIt = trianglesB.begin();

		while (aIt != trianglesA.end() || bIt != trianglesB.end())
		{
			const bool aEnd = aIt == trianglesA.end();
			const bool bEnd = bIt == trianglesB.end();

			if (aEnd || bEnd || *aIt != *bIt)
			{
				log << TestLog::Message << "Failure: triangle sets in two cases are not equal (when ignoring triangle and vertex order"
					<< (ignoredTriangleDescription == DE_NULL ? "" : string() + ", and " + ignoredTriangleDescription) << ")" << TestLog::EndMessage;

				if (!aEnd && (bEnd || TriangleLexLessThan()(*aIt, *bIt)))
					log << TestLog::Message << "Note: e.g. triangle " << *aIt << " exists for first case but not for second" << TestLog::EndMessage;
				else
					log << TestLog::Message << "Note: e.g. triangle " << *bIt << " exists for second case but not for first" << TestLog::EndMessage;

				return false;
			}

			++aIt;
			++bIt;
		}

		return true;
	}
}

static bool compareTriangleSets (const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, TestLog& log)
{
	return compareTriangleSets(coordsA, coordsB, log, ConstantUnaryPredicate<const Vec3*, true>());
}

static void checkExtensionSupport (Context& context, const char* extName)
{
	if (!context.getContextInfo().isExtensionSupported(extName))
		throw tcu::NotSupportedError(string(extName) + " not supported");
}

static void checkTessellationSupport (Context& context)
{
	checkExtensionSupport(context, "GL_EXT_tessellation_shader");
}

// Draw primitives with shared edges and check that no cracks are visible at the shared edges.
class CommonEdgeCase : public TestCase
{
public:
	enum CaseType
	{
		CASETYPE_BASIC = 0,		//!< Order patch vertices such that when two patches share a vertex, it's at the same index for both.
		CASETYPE_PRECISE,		//!< Vertex indices don't match like for CASETYPE_BASIC, but other measures are taken, using the 'precise' qualifier.

		CASETYPE_LAST
	};

	CommonEdgeCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing, CaseType caseType)
		: TestCase			(context, name, description)
		, m_primitiveType	(primitiveType)
		, m_spacing			(spacing)
		, m_caseType		(caseType)
	{
		DE_ASSERT(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES || m_primitiveType == TESSPRIMITIVETYPE_QUADS);
	}

	void							init		(void);
	void							deinit		(void);
	IterateResult					iterate		(void);

private:
	static const int				RENDER_SIZE = 256;

	const TessPrimitiveType			m_primitiveType;
	const SpacingMode				m_spacing;
	const CaseType					m_caseType;

	SharedPtr<const ShaderProgram>	m_program;
};

void CommonEdgeCase::init (void)
{
	checkTessellationSupport(m_context);

	if (m_caseType == CASETYPE_PRECISE)
		checkExtensionSupport(m_context, "GL_EXT_gpu_shader5");

	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
			<< glu::VertexSource					("#version 310 es\n"
													 "\n"
													 "in highp vec2 in_v_position;\n"
													 "in highp float in_v_tessParam;\n"
													 "\n"
													 "out highp vec2 in_tc_position;\n"
													 "out highp float in_tc_tessParam;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	in_tc_position = in_v_position;\n"
													 "	in_tc_tessParam = in_v_tessParam;\n"
													 "}\n")

			<< glu::TessellationControlSource		("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 + string(m_caseType == CASETYPE_PRECISE ? "#extension GL_EXT_gpu_shader5 : require\n" : "") +
													 "\n"
													 "layout (vertices = " + string(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "3" : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? "4" : DE_NULL) + ") out;\n"
													 "\n"
													 "in highp vec2 in_tc_position[];\n"
													 "in highp float in_tc_tessParam[];\n"
													 "\n"
													 "out highp vec2 in_te_position[];\n"
													 "\n"
													 + (m_caseType == CASETYPE_PRECISE ? "precise gl_TessLevelOuter;\n\n" : "") +
													 "void main (void)\n"
													 "{\n"
													 "	in_te_position[gl_InvocationID] = in_tc_position[gl_InvocationID];\n"
													 "\n"
													 "	gl_TessLevelInner[0] = 5.0;\n"
													 "	gl_TessLevelInner[1] = 5.0;\n"
													 "\n"
													 + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
														"	gl_TessLevelOuter[0] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[1] + in_tc_tessParam[2]);\n"
														"	gl_TessLevelOuter[1] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[2] + in_tc_tessParam[0]);\n"
														"	gl_TessLevelOuter[2] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[0] + in_tc_tessParam[1]);\n"
													  : m_primitiveType == TESSPRIMITIVETYPE_QUADS ?
														"	gl_TessLevelOuter[0] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[0] + in_tc_tessParam[2]);\n"
														"	gl_TessLevelOuter[1] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[1] + in_tc_tessParam[0]);\n"
														"	gl_TessLevelOuter[2] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[3] + in_tc_tessParam[1]);\n"
														"	gl_TessLevelOuter[3] = 1.0 + 59.0 * 0.5 * (in_tc_tessParam[2] + in_tc_tessParam[3]);\n"
													  : DE_NULL) +
													 "}\n")

			<< glu::TessellationEvaluationSource	("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 + string(m_caseType == CASETYPE_PRECISE ? "#extension GL_EXT_gpu_shader5 : require\n" : "") +
													 "\n"
													 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing) +
													 "\n"
													 "in highp vec2 in_te_position[];\n"
													 "\n"
													 "out mediump vec4 in_f_color;\n"
													 "\n"
													 + (m_caseType == CASETYPE_PRECISE ? "precise gl_Position;\n\n" : "") +
													 "void main (void)\n"
													 "{\n"
													 + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
														"	highp vec2 pos = gl_TessCoord.x*in_te_position[0] + gl_TessCoord.y*in_te_position[1] + gl_TessCoord.z*in_te_position[2];\n"
														"\n"
														"	highp float f = sqrt(3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z))) * 0.5 + 0.5;\n"
														"	in_f_color = vec4(gl_TessCoord*f, 1.0);\n"
													  : m_primitiveType == TESSPRIMITIVETYPE_QUADS ?
													    string()
														+ (m_caseType == CASETYPE_BASIC ?
															"	highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[0]\n"
															"	               + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[1]\n"
															"	               + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[2]\n"
															"	               + (    gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[3];\n"
														 : m_caseType == CASETYPE_PRECISE ?
															"	highp vec2 a = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[0];\n"
															"	highp vec2 b = (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*in_te_position[1];\n"
															"	highp vec2 c = (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[2];\n"
															"	highp vec2 d = (    gl_TessCoord.x)*(    gl_TessCoord.y)*in_te_position[3];\n"
															"	highp vec2 pos = a+b+c+d;\n"
														 : DE_NULL) +
														"\n"
														"	highp float f = sqrt(1.0 - 2.0 * max(abs(gl_TessCoord.x - 0.5), abs(gl_TessCoord.y - 0.5)))*0.5 + 0.5;\n"
														"	in_f_color = vec4(0.1, gl_TessCoord.xy*f, 1.0);\n"
													  : DE_NULL) +
													 "\n"
													 "	// Offset the position slightly, based on the parity of the bits in the float representation.\n"
													 "	// This is done to detect possible small differences in edge vertex positions between patches.\n"
													 "	uvec2 bits = floatBitsToUint(pos);\n"
													 "	uint numBits = 0u;\n"
													 "	for (uint i = 0u; i < 32u; i++)\n"
													 "		numBits += ((bits[0] >> i) & 1u) + ((bits[1] >> i) & 1u);\n"
													 "	pos += float(numBits&1u)*0.04;\n"
													 "\n"
													 "	gl_Position = vec4(pos, 0.0, 1.0);\n"
													 "}\n")

			<< glu::FragmentSource					("#version 310 es\n"
													 "\n"
													 "layout (location = 0) out mediump vec4 o_color;\n"
													 "\n"
													 "in mediump vec4 in_f_color;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	o_color = in_f_color;\n"
													 "}\n")));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void CommonEdgeCase::deinit (void)
{
	m_program.clear();
}

CommonEdgeCase::IterateResult CommonEdgeCase::iterate (void)
{
	TestLog&					log						= m_testCtx.getLog();
	const RenderContext&		renderCtx				= m_context.getRenderContext();
	const RandomViewport		viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const deUint32				programGL				= m_program->getProgram();
	const glw::Functions&		gl						= renderCtx.getFunctions();

	const int					gridWidth				= 4;
	const int					gridHeight				= 4;
	const int					numVertices				= (gridWidth+1)*(gridHeight+1);
	const int					numIndices				= gridWidth*gridHeight * (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3*2 : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? 4 : -1);
	const int					numPosCompsPerVertex	= 2;
	const int					totalNumPosComps		= numPosCompsPerVertex*numVertices;
	vector<float>				gridPosComps;
	vector<float>				gridTessParams;
	vector<deUint16>			gridIndices;

	gridPosComps.reserve(totalNumPosComps);
	gridTessParams.reserve(numVertices);
	gridIndices.reserve(numIndices);

	{
		for (int i = 0; i < gridHeight+1; i++)
		for (int j = 0; j < gridWidth+1; j++)
		{
			gridPosComps.push_back(-1.0f + 2.0f * ((float)j + 0.5f) / (float)(gridWidth+1));
			gridPosComps.push_back(-1.0f + 2.0f * ((float)i + 0.5f) / (float)(gridHeight+1));
			gridTessParams.push_back((float)(i*(gridWidth+1) + j) / (float)(numVertices-1));
		}
	}

	// Generate patch vertex indices.
	// \note If CASETYPE_BASIC, the vertices are ordered such that when multiple
	//		 triangles/quads share a vertex, it's at the same index for everyone.

	if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
	{
		for (int i = 0; i < gridHeight; i++)
		for (int j = 0; j < gridWidth; j++)
		{
			const deUint16 corners[4] =
			{
				(deUint16)((i+0)*(gridWidth+1) + j+0),
				(deUint16)((i+0)*(gridWidth+1) + j+1),
				(deUint16)((i+1)*(gridWidth+1) + j+0),
				(deUint16)((i+1)*(gridWidth+1) + j+1)
			};

			const int secondTriangleVertexIndexOffset = m_caseType == CASETYPE_BASIC	? 0
													  : m_caseType == CASETYPE_PRECISE	? 1
													  : -1;
			DE_ASSERT(secondTriangleVertexIndexOffset != -1);

			for (int k = 0; k < 3; k++)
				gridIndices.push_back(corners[(k+0 + i + (2-j%3)) % 3]);
			for (int k = 0; k < 3; k++)
				gridIndices.push_back(corners[(k+2 + i + (2-j%3) + secondTriangleVertexIndexOffset) % 3 + 1]);
		}
	}
	else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
	{
		for (int i = 0; i < gridHeight; i++)
		for (int j = 0; j < gridWidth; j++)
		{
			// \note The vertices are ordered such that when multiple quads
			//		 share a vertices, it's at the same index for everyone.
			for (int m = 0; m < 2; m++)
			for (int n = 0; n < 2; n++)
				gridIndices.push_back((deUint16)((i+(i+m)%2)*(gridWidth+1) + j+(j+n)%2));

			if(m_caseType == CASETYPE_PRECISE && (i+j) % 2 == 0)
				std::reverse(gridIndices.begin() + (gridIndices.size() - 4),
							 gridIndices.begin() + gridIndices.size());
		}
	}
	else
		DE_ASSERT(false);

	DE_ASSERT((int)gridPosComps.size() == totalNumPosComps);
	DE_ASSERT((int)gridTessParams.size() == numVertices);
	DE_ASSERT((int)gridIndices.size() == numIndices);

	setViewport(gl, viewport);
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.useProgram(programGL);

	{
		gl.patchParameteri(GL_PATCH_VERTICES, m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? 4 : -1);
		gl.clear(GL_COLOR_BUFFER_BIT);

		const glu::VertexArrayBinding attrBindings[] =
		{
			glu::va::Float("in_v_position", numPosCompsPerVertex, numVertices, 0, &gridPosComps[0]),
			glu::va::Float("in_v_tessParam", 1, numVertices, 0, &gridTessParams[0])
		};

		glu::draw(renderCtx, programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
			glu::pr::Patches((int)gridIndices.size(), &gridIndices[0]));
		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
	}

	{
		const tcu::Surface rendered = getPixels(renderCtx, viewport);

		log << TestLog::Image("RenderedImage", "Rendered Image", rendered)
			<< TestLog::Message << "Note: coloring is done to clarify the positioning and orientation of the "
								<< (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "triangles" : m_primitiveType == TESSPRIMITIVETYPE_QUADS ? "quads" : DE_NULL)
								<< "; the color of a vertex corresponds to the index of that vertex in the patch"
								<< TestLog::EndMessage;

		if (m_caseType == CASETYPE_BASIC)
			log << TestLog::Message << "Note: each shared vertex has the same index among the primitives it belongs to" << TestLog::EndMessage;
		else if (m_caseType == CASETYPE_PRECISE)
			log << TestLog::Message << "Note: the 'precise' qualifier is used to avoid cracks between primitives" << TestLog::EndMessage;
		else
			DE_ASSERT(false);

		// Ad-hoc result verification - check that a certain rectangle in the image contains no black pixels.

		const int startX	= (int)(0.15f * (float)rendered.getWidth());
		const int endX		= (int)(0.85f * (float)rendered.getWidth());
		const int startY	= (int)(0.15f * (float)rendered.getHeight());
		const int endY		= (int)(0.85f * (float)rendered.getHeight());

		for (int y = startY; y < endY; y++)
		for (int x = startX; x < endX; x++)
		{
			const tcu::RGBA pixel = rendered.getPixel(x, y);

			if (pixel.getRed() == 0 && pixel.getGreen() == 0 && pixel.getBlue() == 0)
			{
				log << TestLog::Message << "Failure: there seem to be cracks in the rendered result" << TestLog::EndMessage
					<< TestLog::Message << "Note: pixel with zero r, g and b channels found at " << tcu::IVec2(x, y) << TestLog::EndMessage;

				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
				return STOP;
			}
		}

		log << TestLog::Message << "Success: there seem to be no cracks in the rendered result" << TestLog::EndMessage;
	}

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

// Check tessellation coordinates (read with transform feedback).
class TessCoordCase : public TestCase
{
public:
					TessCoordCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing)
						: TestCase			(context, name, description)
						, m_primitiveType	(primitiveType)
						, m_spacing			(spacing)
					{
					}

	void			init		(void);
	void			deinit		(void);
	IterateResult	iterate		(void);

private:
	struct TessLevels
	{
		float inner[2];
		float outer[4];
	};

	static const int				RENDER_SIZE = 16;

	vector<TessLevels>				genTessLevelCases (void) const;

	const TessPrimitiveType			m_primitiveType;
	const SpacingMode				m_spacing;

	SharedPtr<const ShaderProgram>	m_program;
};

void TessCoordCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
			<< glu::VertexSource					("#version 310 es\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "}\n")

			<< glu::TessellationControlSource		("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 "layout (vertices = 1) out;\n"
													 "\n"
													 "uniform mediump float u_tessLevelInner0;\n"
													 "uniform mediump float u_tessLevelInner1;\n"
													 "\n"
													 "uniform mediump float u_tessLevelOuter0;\n"
													 "uniform mediump float u_tessLevelOuter1;\n"
													 "uniform mediump float u_tessLevelOuter2;\n"
													 "uniform mediump float u_tessLevelOuter3;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	gl_TessLevelInner[0] = u_tessLevelInner0;\n"
													 "	gl_TessLevelInner[1] = u_tessLevelInner1;\n"
													 "\n"
													 "	gl_TessLevelOuter[0] = u_tessLevelOuter0;\n"
													 "	gl_TessLevelOuter[1] = u_tessLevelOuter1;\n"
													 "	gl_TessLevelOuter[2] = u_tessLevelOuter2;\n"
													 "	gl_TessLevelOuter[3] = u_tessLevelOuter3;\n"
													 "}\n")

			<< glu::TessellationEvaluationSource	("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, true) +
													 "\n"
													 "out highp vec3 out_te_tessCoord;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	out_te_tessCoord = gl_TessCoord;\n"
													 "	gl_Position = vec4(gl_TessCoord.xy*1.6 - 0.8, 0.0, 1.0);\n"
													 "}\n")

			<< glu::FragmentSource					("#version 310 es\n"
													 "\n"
													 "layout (location = 0) out mediump vec4 o_color;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	o_color = vec4(1.0);\n"
													 "}\n")

			<< glu::TransformFeedbackVarying		("out_te_tessCoord")
			<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void TessCoordCase::deinit (void)
{
	m_program.clear();
}

vector<TessCoordCase::TessLevels> TessCoordCase::genTessLevelCases (void) const
{
	static const TessLevels rawTessLevelCases[] =
	{
		{ { 1.0f,	1.0f	},	{ 1.0f,		1.0f,	1.0f,	1.0f	} },
		{ { 63.0f,	24.0f	},	{ 15.0f,	42.0f,	10.0f,	12.0f	} },
		{ { 3.0f,	2.0f	},	{ 6.0f,		8.0f,	7.0f,	9.0f	} },
		{ { 4.0f,	6.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
		{ { 2.0f,	2.0f	},	{ 6.0f,		8.0f,	7.0f,	9.0f	} },
		{ { 5.0f,	6.0f	},	{ 1.0f,		1.0f,	1.0f,	1.0f	} },
		{ { 1.0f,	6.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
		{ { 5.0f,	1.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
		{ { 5.2f,	1.6f	},	{ 2.9f,		3.4f,	1.5f,	4.1f	} }
	};

	if (m_spacing == SPACINGMODE_EQUAL)
		return vector<TessLevels>(DE_ARRAY_BEGIN(rawTessLevelCases), DE_ARRAY_END(rawTessLevelCases));
	else
	{
		vector<TessLevels> result;
		result.reserve(DE_LENGTH_OF_ARRAY(rawTessLevelCases));

		for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < DE_LENGTH_OF_ARRAY(rawTessLevelCases); tessLevelCaseNdx++)
		{
			TessLevels curTessLevelCase = rawTessLevelCases[tessLevelCaseNdx];

			float* const inner = &curTessLevelCase.inner[0];
			float* const outer = &curTessLevelCase.outer[0];

			for (int j = 0; j < 2; j++) inner[j] = (float)getClampedRoundedTessLevel(m_spacing, inner[j]);
			for (int j = 0; j < 4; j++) outer[j] = (float)getClampedRoundedTessLevel(m_spacing, outer[j]);

			if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
			{
				if (outer[0] > 1.0f || outer[1] > 1.0f || outer[2] > 1.0f)
				{
					if (inner[0] == 1.0f)
						inner[0] = (float)getClampedRoundedTessLevel(m_spacing, inner[0] + 0.1f);
				}
			}
			else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
			{
				if (outer[0] > 1.0f || outer[1] > 1.0f || outer[2] > 1.0f || outer[3] > 1.0f)
				{
					if (inner[0] == 1.0f) inner[0] = (float)getClampedRoundedTessLevel(m_spacing, inner[0] + 0.1f);
					if (inner[1] == 1.0f) inner[1] = (float)getClampedRoundedTessLevel(m_spacing, inner[1] + 0.1f);
				}
			}

			result.push_back(curTessLevelCase);
		}

		DE_ASSERT((int)result.size() == DE_LENGTH_OF_ARRAY(rawTessLevelCases));
		return result;
	}
}

TessCoordCase::IterateResult TessCoordCase::iterate (void)
{
	typedef TransformFeedbackHandler<Vec3> TFHandler;

	TestLog&						log							= m_testCtx.getLog();
	const RenderContext&			renderCtx					= m_context.getRenderContext();
	const RandomViewport			viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const deUint32					programGL					= m_program->getProgram();
	const glw::Functions&			gl							= renderCtx.getFunctions();

	const int						tessLevelInner0Loc			= gl.getUniformLocation(programGL, "u_tessLevelInner0");
	const int						tessLevelInner1Loc			= gl.getUniformLocation(programGL, "u_tessLevelInner1");
	const int						tessLevelOuter0Loc			= gl.getUniformLocation(programGL, "u_tessLevelOuter0");
	const int						tessLevelOuter1Loc			= gl.getUniformLocation(programGL, "u_tessLevelOuter1");
	const int						tessLevelOuter2Loc			= gl.getUniformLocation(programGL, "u_tessLevelOuter2");
	const int						tessLevelOuter3Loc			= gl.getUniformLocation(programGL, "u_tessLevelOuter3");

	const vector<TessLevels>		tessLevelCases				= genTessLevelCases();
	vector<vector<Vec3> >			caseReferences				(tessLevelCases.size());

	for (int i = 0; i < (int)tessLevelCases.size(); i++)
		caseReferences[i] = generateReferenceTessCoords(m_primitiveType, m_spacing, &tessLevelCases[i].inner[0], &tessLevelCases[i].outer[0]);

	const int						maxNumVertices				= (int)std::max_element(caseReferences.begin(), caseReferences.end(), SizeLessThan<vector<Vec3> >())->size();
	const TFHandler					tfHandler					(m_context.getRenderContext(), maxNumVertices);

	bool							success						= true;

	setViewport(gl, viewport);
	gl.useProgram(programGL);

	gl.patchParameteri(GL_PATCH_VERTICES, 1);

	for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++)
	{
		const float* const innerLevels = &tessLevelCases[tessLevelCaseNdx].inner[0];
		const float* const outerLevels = &tessLevelCases[tessLevelCaseNdx].outer[0];

		log << TestLog::Message << "Tessellation levels: " << tessellationLevelsString(innerLevels, outerLevels, m_primitiveType) << TestLog::EndMessage;

		gl.uniform1f(tessLevelInner0Loc, innerLevels[0]);
		gl.uniform1f(tessLevelInner1Loc, innerLevels[1]);
		gl.uniform1f(tessLevelOuter0Loc, outerLevels[0]);
		gl.uniform1f(tessLevelOuter1Loc, outerLevels[1]);
		gl.uniform1f(tessLevelOuter2Loc, outerLevels[2]);
		gl.uniform1f(tessLevelOuter3Loc, outerLevels[3]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");

		{
			const vector<Vec3>&			tessCoordsRef	= caseReferences[tessLevelCaseNdx];
			const TFHandler::Result		tfResult		= tfHandler.renderAndGetPrimitives(programGL, GL_POINTS, 0, DE_NULL, 1);

			if (tfResult.numPrimitives != (int)tessCoordsRef.size())
			{
				log << TestLog::Message << "Failure: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be "
										<< tfResult.numPrimitives << ", reference value is " << tessCoordsRef.size()
										<< " (logging further info anyway)" << TestLog::EndMessage;
				success = false;
			}
			else
				log << TestLog::Message << "Note: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be " << tfResult.numPrimitives << TestLog::EndMessage;

			if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
				log << TestLog::Message << "Note: in the following visualization(s), the u=1, v=1, w=1 corners are at the right, top, and left corners, respectively" << TestLog::EndMessage;
			else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS || m_primitiveType == TESSPRIMITIVETYPE_ISOLINES)
				log << TestLog::Message << "Note: in the following visualization(s), u and v coordinate go left-to-right and bottom-to-top, respectively" << TestLog::EndMessage;
			else
				DE_ASSERT(false);

			success = compareTessCoords(log, m_primitiveType, tessCoordsRef, tfResult.varying) && success;
		}

		if (!success)
			break;
		else
			log << TestLog::Message << "All OK" << TestLog::EndMessage;
	}

	m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Invalid tessellation coordinates");
	return STOP;
}

// Check validity of fractional spacing modes. Draws a single isoline, reads tesscoords with transform feedback.
class FractionalSpacingModeCase : public TestCase
{
public:
					FractionalSpacingModeCase (Context& context, const char* name, const char* description, SpacingMode spacing)
						: TestCase	(context, name, description)
						, m_spacing	(spacing)
					{
						DE_ASSERT(m_spacing == SPACINGMODE_FRACTIONAL_EVEN || m_spacing == SPACINGMODE_FRACTIONAL_ODD);
					}

	void			init		(void);
	void			deinit		(void);
	IterateResult	iterate		(void);

private:
	static const int				RENDER_SIZE = 16;

	static vector<float>			genTessLevelCases (void);

	const SpacingMode				m_spacing;

	SharedPtr<const ShaderProgram>	m_program;
};

void FractionalSpacingModeCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
			<< glu::VertexSource					("#version 310 es\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "}\n")

			<< glu::TessellationControlSource		("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 "layout (vertices = 1) out;\n"
													 "\n"
													 "uniform mediump float u_tessLevelOuter1;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	gl_TessLevelOuter[0] = 1.0;\n"
													 "	gl_TessLevelOuter[1] = u_tessLevelOuter1;\n"
													 "}\n")

			<< glu::TessellationEvaluationSource	("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_ISOLINES, m_spacing, true) +
													 "\n"
													 "out highp float out_te_tessCoord;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	out_te_tessCoord = gl_TessCoord.x;\n"
													 "	gl_Position = vec4(gl_TessCoord.xy*1.6 - 0.8, 0.0, 1.0);\n"
													 "}\n")

			<< glu::FragmentSource					("#version 310 es\n"
													 "\n"
													 "layout (location = 0) out mediump vec4 o_color;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	o_color = vec4(1.0);\n"
													 "}\n")

			<< glu::TransformFeedbackVarying		("out_te_tessCoord")
			<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void FractionalSpacingModeCase::deinit (void)
{
	m_program.clear();
}

vector<float> FractionalSpacingModeCase::genTessLevelCases (void)
{
	vector<float> result;

	// Ranges [7.0 .. 8.0), [8.0 .. 9.0) and [9.0 .. 10.0)
	{
		static const float	rangeStarts[]		= { 7.0f, 8.0f, 9.0f };
		const int			numSamplesPerRange	= 10;

		for (int rangeNdx = 0; rangeNdx < DE_LENGTH_OF_ARRAY(rangeStarts); rangeNdx++)
			for (int i = 0; i < numSamplesPerRange; i++)
				result.push_back(rangeStarts[rangeNdx] + (float)i/(float)numSamplesPerRange);
	}

	// 0.3, 1.3, 2.3,  ... , 62.3
	for (int i = 0; i <= 62; i++)
		result.push_back((float)i + 0.3f);

	return result;
}

FractionalSpacingModeCase::IterateResult FractionalSpacingModeCase::iterate (void)
{
	typedef TransformFeedbackHandler<float> TFHandler;

	TestLog&						log							= m_testCtx.getLog();
	const RenderContext&			renderCtx					= m_context.getRenderContext();
	const RandomViewport			viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const deUint32					programGL					= m_program->getProgram();
	const glw::Functions&			gl							= renderCtx.getFunctions();

	const int						tessLevelOuter1Loc			= gl.getUniformLocation(programGL, "u_tessLevelOuter1");

	// Second outer tessellation levels.
	const vector<float>				tessLevelCases				= genTessLevelCases();
	const int						maxNumVertices				= 1 + getClampedRoundedTessLevel(m_spacing, *std::max_element(tessLevelCases.begin(), tessLevelCases.end()));
	vector<float>					additionalSegmentLengths;
	vector<int>						additionalSegmentLocations;

	const TFHandler					tfHandler					(m_context.getRenderContext(), maxNumVertices);

	bool							success						= true;

	setViewport(gl, viewport);
	gl.useProgram(programGL);

	gl.patchParameteri(GL_PATCH_VERTICES, 1);

	for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++)
	{
		const float outerLevel1 = tessLevelCases[tessLevelCaseNdx];

		gl.uniform1f(tessLevelOuter1Loc, outerLevel1);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Setup failed");

		{
			const TFHandler::Result		tfResult = tfHandler.renderAndGetPrimitives(programGL, GL_POINTS, 0, DE_NULL, 1);
			float						additionalSegmentLength;
			int							additionalSegmentLocation;

			success = verifyFractionalSpacingSingle(log, m_spacing, outerLevel1, tfResult.varying,
													additionalSegmentLength, additionalSegmentLocation);

			if (!success)
				break;

			additionalSegmentLengths.push_back(additionalSegmentLength);
			additionalSegmentLocations.push_back(additionalSegmentLocation);
		}
	}

	if (success)
		success = verifyFractionalSpacingMultiple(log, m_spacing, tessLevelCases, additionalSegmentLengths, additionalSegmentLocations);

	m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Invalid tessellation coordinates");
	return STOP;
}

// Base class for a case with one input attribute (in_v_position) and optionally a TCS; tests with a couple of different sets of tessellation levels.
class BasicVariousTessLevelsPosAttrCase : public TestCase
{
public:
					BasicVariousTessLevelsPosAttrCase (Context&				context,
													   const char*			name,
													   const char*			description,
													   TessPrimitiveType	primitiveType,
													   SpacingMode			spacing,
													   const char*			referenceImagePathPrefix)
						: TestCase						(context, name, description)
						, m_primitiveType				(primitiveType)
						, m_spacing						(spacing)
						, m_referenceImagePathPrefix	(referenceImagePathPrefix)
					{
					}

	void			init			(void);
	void			deinit			(void);
	IterateResult	iterate			(void);

protected:
	virtual const glu::ProgramSources	makeSources (TessPrimitiveType, SpacingMode, const char* vtxOutPosAttrName) const = DE_NULL;

private:
	static const int					RENDER_SIZE = 256;

	const TessPrimitiveType				m_primitiveType;
	const SpacingMode					m_spacing;
	const string						m_referenceImagePathPrefix;

	SharedPtr<const ShaderProgram>		m_program;
};

void BasicVariousTessLevelsPosAttrCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	{
		glu::ProgramSources sources = makeSources(m_primitiveType, m_spacing, "in_tc_position");
		DE_ASSERT(sources.sources[glu::SHADERTYPE_TESSELLATION_CONTROL].empty());

		sources << glu::TessellationControlSource(	"#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													"\n"
													"layout (vertices = " + string(m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? "3" : "4") + ") out;\n"
													"\n"
													"in highp vec2 in_tc_position[];\n"
													"\n"
													"out highp vec2 in_te_position[];\n"
													"\n"
													"uniform mediump float u_tessLevelInner0;\n"
													"uniform mediump float u_tessLevelInner1;\n"
													"uniform mediump float u_tessLevelOuter0;\n"
													"uniform mediump float u_tessLevelOuter1;\n"
													"uniform mediump float u_tessLevelOuter2;\n"
													"uniform mediump float u_tessLevelOuter3;\n"
													"\n"
													"void main (void)\n"
													"{\n"
													"	in_te_position[gl_InvocationID] = in_tc_position[gl_InvocationID];\n"
													"\n"
													"	gl_TessLevelInner[0] = u_tessLevelInner0;\n"
													"	gl_TessLevelInner[1] = u_tessLevelInner1;\n"
													"\n"
													"	gl_TessLevelOuter[0] = u_tessLevelOuter0;\n"
													"	gl_TessLevelOuter[1] = u_tessLevelOuter1;\n"
													"	gl_TessLevelOuter[2] = u_tessLevelOuter2;\n"
													"	gl_TessLevelOuter[3] = u_tessLevelOuter3;\n"
													"}\n");

		m_program = SharedPtr<const ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(), sources));
	}

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void BasicVariousTessLevelsPosAttrCase::deinit (void)
{
	m_program.clear();
}

BasicVariousTessLevelsPosAttrCase::IterateResult BasicVariousTessLevelsPosAttrCase::iterate (void)
{
	static const struct
	{
		float inner[2];
		float outer[4];
	} tessLevelCases[] =
	{
		{ { 9.0f,	9.0f	},	{ 9.0f,		9.0f,	9.0f,	9.0f	} },
		{ { 8.0f,	11.0f	},	{ 13.0f,	15.0f,	18.0f,	21.0f	} },
		{ { 17.0f,	14.0f	},	{ 3.0f,		6.0f,	9.0f,	12.0f	} }
	};

	TestLog&				log					= m_testCtx.getLog();
	const RenderContext&	renderCtx			= m_context.getRenderContext();
	const RandomViewport	viewport			(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const deUint32			programGL			= m_program->getProgram();
	const glw::Functions&	gl					= renderCtx.getFunctions();
	const int				patchSize			= m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES	? 3
												: m_primitiveType == TESSPRIMITIVETYPE_QUADS		? 4
												: m_primitiveType == TESSPRIMITIVETYPE_ISOLINES		? 4
												: -1;

	setViewport(gl, viewport);
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.useProgram(programGL);

	gl.patchParameteri(GL_PATCH_VERTICES, patchSize);

	for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < DE_LENGTH_OF_ARRAY(tessLevelCases); tessLevelCaseNdx++)
	{
		float innerLevels[2];
		float outerLevels[4];

		for (int i = 0; i < DE_LENGTH_OF_ARRAY(innerLevels); i++)
			innerLevels[i] = (float)getClampedRoundedTessLevel(m_spacing, tessLevelCases[tessLevelCaseNdx].inner[i]);

		for (int i = 0; i < DE_LENGTH_OF_ARRAY(outerLevels); i++)
			outerLevels[i] = (float)getClampedRoundedTessLevel(m_spacing, tessLevelCases[tessLevelCaseNdx].outer[i]);

		log << TestLog::Message << "Tessellation levels: " << tessellationLevelsString(&innerLevels[0], &outerLevels[0], m_primitiveType) << TestLog::EndMessage;

		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelInner0"), innerLevels[0]);
		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelInner1"), innerLevels[1]);
		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter0"), outerLevels[0]);
		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter1"), outerLevels[1]);
		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter2"), outerLevels[2]);
		gl.uniform1f(gl.getUniformLocation(programGL, "u_tessLevelOuter3"), outerLevels[3]);

		gl.clear(GL_COLOR_BUFFER_BIT);

		{
			vector<Vec2> positions;
			positions.reserve(4);

			if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
			{
				positions.push_back(Vec2( 0.8f,    0.6f));
				positions.push_back(Vec2( 0.0f, -0.786f));
				positions.push_back(Vec2(-0.8f,    0.6f));
			}
			else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS || m_primitiveType == TESSPRIMITIVETYPE_ISOLINES)
			{
				positions.push_back(Vec2(-0.8f, -0.8f));
				positions.push_back(Vec2( 0.8f, -0.8f));
				positions.push_back(Vec2(-0.8f,  0.8f));
				positions.push_back(Vec2( 0.8f,  0.8f));
			}
			else
				DE_ASSERT(false);

			DE_ASSERT((int)positions.size() == patchSize);

			const glu::VertexArrayBinding attrBindings[] =
			{
				glu::va::Float("in_v_position", 2, (int)positions.size(), 0, &positions[0].x())
			};

			glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
				glu::pr::Patches(patchSize));
			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
		}

		{
			const tcu::Surface			rendered	= getPixels(renderCtx, viewport);
			const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePathPrefix + "_" + de::toString(tessLevelCaseNdx) + ".png");
			const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), rendered.getAccess(), 0.002f, tcu::COMPARE_LOG_RESULT);

			if (!success)
			{
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
				return STOP;
			}
		}
	}

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

// Test that there are no obvious gaps in the triangulation of a tessellated triangle or quad.
class BasicTriangleFillCoverCase : public BasicVariousTessLevelsPosAttrCase
{
public:
	BasicTriangleFillCoverCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing, const char* referenceImagePathPrefix)
		: BasicVariousTessLevelsPosAttrCase (context, name, description, primitiveType, spacing, referenceImagePathPrefix)
	{
		DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
	}

protected:
	void init (void)
	{
		checkExtensionSupport(m_context, "GL_EXT_gpu_shader5");
		BasicVariousTessLevelsPosAttrCase::init();
	}

	const glu::ProgramSources makeSources (TessPrimitiveType primitiveType, SpacingMode spacing, const char* vtxOutPosAttrName) const
	{
		return glu::ProgramSources()
			<< glu::VertexSource					("#version 310 es\n"
													 "\n"
													 "in highp vec2 in_v_position;\n"
													 "\n"
													 "out highp vec2 " + string(vtxOutPosAttrName) + ";\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	" + vtxOutPosAttrName + " = in_v_position;\n"
													 "}\n")

			<< glu::TessellationEvaluationSource	("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "#extension GL_EXT_gpu_shader5 : require\n"
													 "\n"
													 + getTessellationEvaluationInLayoutString(primitiveType, spacing) +
													 "\n"
													 "in highp vec2 in_te_position[];\n"
													 "\n"
													 "precise gl_Position;\n"
													 "void main (void)\n"
													 "{\n"
													 + (primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
														"\n"
														"	highp float d = 3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z));\n"
														"	highp vec2 corner0 = in_te_position[0];\n"
														"	highp vec2 corner1 = in_te_position[1];\n"
														"	highp vec2 corner2 = in_te_position[2];\n"
														"	highp vec2 pos =  corner0*gl_TessCoord.x + corner1*gl_TessCoord.y + corner2*gl_TessCoord.z;\n"
														"	highp vec2 fromCenter = pos - (corner0 + corner1 + corner2) / 3.0;\n"
														"	highp float f = (1.0 - length(fromCenter)) * (1.5 - d);\n"
														"	pos += 0.75 * f * fromCenter / (length(fromCenter) + 0.3);\n"
														"	gl_Position = vec4(pos, 0.0, 1.0);\n"
													  : primitiveType == TESSPRIMITIVETYPE_QUADS ?
														"	highp vec2 corner0 = in_te_position[0];\n"
														"	highp vec2 corner1 = in_te_position[1];\n"
														"	highp vec2 corner2 = in_te_position[2];\n"
														"	highp vec2 corner3 = in_te_position[3];\n"
														"	highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner0\n"
														"	               + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner1\n"
														"	               + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*corner2\n"
														"	               + (    gl_TessCoord.x)*(    gl_TessCoord.y)*corner3;\n"
														"	highp float d = 2.0 * min(abs(gl_TessCoord.x-0.5), abs(gl_TessCoord.y-0.5));\n"
														"	highp vec2 fromCenter = pos - (corner0 + corner1 + corner2 + corner3) / 4.0;\n"
														"	highp float f = (1.0 - length(fromCenter)) * sqrt(1.7 - d);\n"
														"	pos += 0.75 * f * fromCenter / (length(fromCenter) + 0.3);\n"
														"	gl_Position = vec4(pos, 0.0, 1.0);\n"
													  : DE_NULL) +
													 "}\n")

			<< glu::FragmentSource					("#version 310 es\n"
													 "\n"
													 "layout (location = 0) out mediump vec4 o_color;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	o_color = vec4(1.0);\n"
													 "}\n");
	}
};

// Check that there are no obvious overlaps in the triangulation of a tessellated triangle or quad.
class BasicTriangleFillNonOverlapCase : public BasicVariousTessLevelsPosAttrCase
{
public:
	BasicTriangleFillNonOverlapCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, SpacingMode spacing, const char* referenceImagePathPrefix)
		: BasicVariousTessLevelsPosAttrCase (context, name, description, primitiveType, spacing, referenceImagePathPrefix)
	{
		DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
	}

protected:
	const glu::ProgramSources makeSources (TessPrimitiveType primitiveType, SpacingMode spacing, const char* vtxOutPosAttrName) const
	{
		return glu::ProgramSources()
			<< glu::VertexSource					("#version 310 es\n"
													 "\n"
													 "in highp vec2 in_v_position;\n"
													 "\n"
													 "out highp vec2 " + string(vtxOutPosAttrName) + ";\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	" + vtxOutPosAttrName + " = in_v_position;\n"
													 "}\n")

			<< glu::TessellationEvaluationSource	("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 + getTessellationEvaluationInLayoutString(primitiveType, spacing) +
													 "\n"
													 "in highp vec2 in_te_position[];\n"
													 "\n"
													 "out mediump vec4 in_f_color;\n"
													 "\n"
													 "uniform mediump float u_tessLevelInner0;\n"
													 "uniform mediump float u_tessLevelInner1;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 + (primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
														"\n"
														"	highp vec2 corner0 = in_te_position[0];\n"
														"	highp vec2 corner1 = in_te_position[1];\n"
														"	highp vec2 corner2 = in_te_position[2];\n"
														"	highp vec2 pos =  corner0*gl_TessCoord.x + corner1*gl_TessCoord.y + corner2*gl_TessCoord.z;\n"
														"	gl_Position = vec4(pos, 0.0, 1.0);\n"
														"	highp int numConcentricTriangles = int(round(u_tessLevelInner0)) / 2 + 1;\n"
														"	highp float d = 3.0 * min(gl_TessCoord.x, min(gl_TessCoord.y, gl_TessCoord.z));\n"
														"	highp int phase = int(d*float(numConcentricTriangles)) % 3;\n"
														"	in_f_color = phase == 0 ? vec4(1.0, 0.0, 0.0, 1.0)\n"
														"	           : phase == 1 ? vec4(0.0, 1.0, 0.0, 1.0)\n"
														"	           :              vec4(0.0, 0.0, 1.0, 1.0);\n"
													  : primitiveType == TESSPRIMITIVETYPE_QUADS ?
														"	highp vec2 corner0 = in_te_position[0];\n"
														"	highp vec2 corner1 = in_te_position[1];\n"
														"	highp vec2 corner2 = in_te_position[2];\n"
														"	highp vec2 corner3 = in_te_position[3];\n"
														"	highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner0\n"
														"	               + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner1\n"
														"	               + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*corner2\n"
														"	               + (    gl_TessCoord.x)*(    gl_TessCoord.y)*corner3;\n"
														"	gl_Position = vec4(pos, 0.0, 1.0);\n"
														"	highp int phaseX = int(round((0.5 - abs(gl_TessCoord.x-0.5)) * u_tessLevelInner0));\n"
														"	highp int phaseY = int(round((0.5 - abs(gl_TessCoord.y-0.5)) * u_tessLevelInner1));\n"
														"	highp int phase = min(phaseX, phaseY) % 3;\n"
														"	in_f_color = phase == 0 ? vec4(1.0, 0.0, 0.0, 1.0)\n"
														"	           : phase == 1 ? vec4(0.0, 1.0, 0.0, 1.0)\n"
														"	           :              vec4(0.0, 0.0, 1.0, 1.0);\n"
													  : DE_NULL) +
													 "}\n")

			<< glu::FragmentSource					("#version 310 es\n"
													 "\n"
													 "layout (location = 0) out mediump vec4 o_color;\n"
													 "\n"
													 "in mediump vec4 in_f_color;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	o_color = in_f_color;\n"
													 "}\n");
	}
};

// Basic isolines rendering case.
class IsolinesRenderCase : public BasicVariousTessLevelsPosAttrCase
{
public:
	IsolinesRenderCase (Context& context, const char* name, const char* description, SpacingMode spacing, const char* referenceImagePathPrefix)
		: BasicVariousTessLevelsPosAttrCase (context, name, description, TESSPRIMITIVETYPE_ISOLINES, spacing, referenceImagePathPrefix)
	{
	}

protected:
	const glu::ProgramSources makeSources (TessPrimitiveType primitiveType, SpacingMode spacing, const char* vtxOutPosAttrName) const
	{
		DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_ISOLINES);
		DE_UNREF(primitiveType);

		return glu::ProgramSources()
			<< glu::VertexSource					("#version 310 es\n"
													 "\n"
													 "in highp vec2 in_v_position;\n"
													 "\n"
													 "out highp vec2 " + string(vtxOutPosAttrName) + ";\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	" + vtxOutPosAttrName + " = in_v_position;\n"
													 "}\n")

			<< glu::TessellationEvaluationSource	("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_ISOLINES, spacing) +
													 "\n"
													 "in highp vec2 in_te_position[];\n"
													 "\n"
													 "out mediump vec4 in_f_color;\n"
													 "\n"
													 "uniform mediump float u_tessLevelOuter0;\n"
													 "uniform mediump float u_tessLevelOuter1;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	highp vec2 corner0 = in_te_position[0];\n"
													 "	highp vec2 corner1 = in_te_position[1];\n"
													 "	highp vec2 corner2 = in_te_position[2];\n"
													 "	highp vec2 corner3 = in_te_position[3];\n"
													 "	highp vec2 pos = (1.0-gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner0\n"
													 "	               + (    gl_TessCoord.x)*(1.0-gl_TessCoord.y)*corner1\n"
													 "	               + (1.0-gl_TessCoord.x)*(    gl_TessCoord.y)*corner2\n"
													 "	               + (    gl_TessCoord.x)*(    gl_TessCoord.y)*corner3;\n"
													 "	pos.y += 0.15*sin(gl_TessCoord.x*10.0);\n"
													 "	gl_Position = vec4(pos, 0.0, 1.0);\n"
													 "	highp int phaseX = int(round(gl_TessCoord.x*u_tessLevelOuter1));\n"
													 "	highp int phaseY = int(round(gl_TessCoord.y*u_tessLevelOuter0));\n"
													 "	highp int phase = (phaseX + phaseY) % 3;\n"
													 "	in_f_color = phase == 0 ? vec4(1.0, 0.0, 0.0, 1.0)\n"
													 "	           : phase == 1 ? vec4(0.0, 1.0, 0.0, 1.0)\n"
													 "	           :              vec4(0.0, 0.0, 1.0, 1.0);\n"
													 "}\n")

			<< glu::FragmentSource					("#version 310 es\n"
													 "\n"
													 "layout (location = 0) out mediump vec4 o_color;\n"
													 "\n"
													 "in mediump vec4 in_f_color;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	o_color = in_f_color;\n"
													 "}\n");
	}
};

// Test the "cw" and "ccw" TES input layout qualifiers.
class WindingCase : public TestCase
{
public:
					WindingCase (Context& context, const char* name, const char* description, TessPrimitiveType primitiveType, Winding winding)
						: TestCase			(context, name, description)
						, m_primitiveType	(primitiveType)
						, m_winding			(winding)
					{
						DE_ASSERT(primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS);
					}

	void			init		(void);
	void			deinit		(void);
	IterateResult	iterate		(void);

private:
	static const int				RENDER_SIZE = 64;

	const TessPrimitiveType			m_primitiveType;
	const Winding					m_winding;

	SharedPtr<const ShaderProgram>	m_program;
};

void WindingCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
			<< glu::VertexSource					("#version 310 es\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "}\n")

			<< glu::TessellationControlSource		("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 "layout (vertices = 1) out;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	gl_TessLevelInner[0] = 5.0;\n"
													 "	gl_TessLevelInner[1] = 5.0;\n"
													 "\n"
													 "	gl_TessLevelOuter[0] = 5.0;\n"
													 "	gl_TessLevelOuter[1] = 5.0;\n"
													 "	gl_TessLevelOuter[2] = 5.0;\n"
													 "	gl_TessLevelOuter[3] = 5.0;\n"
													 "}\n")

			<< glu::TessellationEvaluationSource	("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 + getTessellationEvaluationInLayoutString(m_primitiveType, m_winding) +
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	gl_Position = vec4(gl_TessCoord.xy*2.0 - 1.0, 0.0, 1.0);\n"
													 "}\n")

			<< glu::FragmentSource					("#version 310 es\n"
													 "\n"
													 "layout (location = 0) out mediump vec4 o_color;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	o_color = vec4(1.0);\n"
													 "}\n")));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void WindingCase::deinit (void)
{
	m_program.clear();
}

WindingCase::IterateResult WindingCase::iterate (void)
{
	TestLog&						log							= m_testCtx.getLog();
	const RenderContext&			renderCtx					= m_context.getRenderContext();
	const RandomViewport			viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const deUint32					programGL					= m_program->getProgram();
	const glw::Functions&			gl							= renderCtx.getFunctions();
	const glu::VertexArray			vao							(renderCtx);

	bool							success						= true;

	setViewport(gl, viewport);
	gl.clearColor(1.0f, 0.0f, 0.0f, 1.0f);
	gl.useProgram(programGL);

	gl.patchParameteri(GL_PATCH_VERTICES, 1);

	gl.enable(GL_CULL_FACE);

	gl.bindVertexArray(*vao);

	log << TestLog::Message << "Face culling enabled" << TestLog::EndMessage;

	for (int frontFaceWinding = 0; frontFaceWinding < WINDING_LAST; frontFaceWinding++)
	{
		log << TestLog::Message << "Setting glFrontFace(" << (frontFaceWinding == WINDING_CW ? "GL_CW" : "GL_CCW") << ")" << TestLog::EndMessage;

		gl.frontFace(frontFaceWinding == WINDING_CW ? GL_CW : GL_CCW);

		gl.clear(GL_COLOR_BUFFER_BIT);
		gl.drawArrays(GL_PATCHES, 0, 1);
		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");

		{
			const tcu::Surface rendered = getPixels(renderCtx, viewport);
			log << TestLog::Image("RenderedImage", "Rendered Image", rendered);

			{
				const int totalNumPixels		= rendered.getWidth()*rendered.getHeight();
				const int badPixelTolerance		= m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 5*de::max(rendered.getWidth(), rendered.getHeight()) : 0;

				int numWhitePixels	= 0;
				int numRedPixels	= 0;
				for (int y = 0; y < rendered.getHeight();	y++)
				for (int x = 0; x < rendered.getWidth();	x++)
				{
					numWhitePixels	+= rendered.getPixel(x, y) == tcu::RGBA::white()	? 1 : 0;
					numRedPixels	+= rendered.getPixel(x, y) == tcu::RGBA::red()	? 1 : 0;
				}

				DE_ASSERT(numWhitePixels + numRedPixels <= totalNumPixels);

				log << TestLog::Message << "Note: got " << numWhitePixels << " white and " << numRedPixels << " red pixels" << TestLog::EndMessage;

				if (totalNumPixels - numWhitePixels - numRedPixels > badPixelTolerance)
				{
					log << TestLog::Message << "Failure: Got " << totalNumPixels - numWhitePixels - numRedPixels << " other than white or red pixels (maximum tolerance " << badPixelTolerance << ")" << TestLog::EndMessage;
					success = false;
					break;
				}

				if ((Winding)frontFaceWinding == m_winding)
				{
					if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
					{
						if (de::abs(numWhitePixels - totalNumPixels/2) > badPixelTolerance)
						{
							log << TestLog::Message << "Failure: wrong number of white pixels; expected approximately " << totalNumPixels/2 << TestLog::EndMessage;
							success = false;
							break;
						}
					}
					else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
					{
						if (numWhitePixels != totalNumPixels)
						{
							log << TestLog::Message << "Failure: expected only white pixels (full-viewport quad)" << TestLog::EndMessage;
							success = false;
							break;
						}
					}
					else
						DE_ASSERT(false);
				}
				else
				{
					if (numWhitePixels != 0)
					{
						log << TestLog::Message << "Failure: expected only red pixels (everything culled)" << TestLog::EndMessage;
						success = false;
						break;
					}
				}
			}
		}
	}

	m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Image verification failed");
	return STOP;
}

// Test potentially differing input and output patch sizes.
class PatchVertexCountCase : public TestCase
{
public:
	PatchVertexCountCase (Context& context, const char* name, const char* description, int inputPatchSize, int outputPatchSize, const char* referenceImagePath)
		: TestCase				(context, name, description)
		, m_inputPatchSize		(inputPatchSize)
		, m_outputPatchSize		(outputPatchSize)
		, m_referenceImagePath	(referenceImagePath)
	{
	}

	void							init				(void);
	void							deinit				(void);
	IterateResult					iterate				(void);

private:
	static const int				RENDER_SIZE = 256;

	const int						m_inputPatchSize;
	const int						m_outputPatchSize;

	const string					m_referenceImagePath;

	SharedPtr<const ShaderProgram>	m_program;
};

void PatchVertexCountCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	const string inSizeStr		= de::toString(m_inputPatchSize);
	const string outSizeStr		= de::toString(m_outputPatchSize);

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
			<< glu::VertexSource					("#version 310 es\n"
													 "\n"
													 "in highp float in_v_attr;\n"
													 "\n"
													 "out highp float in_tc_attr;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	in_tc_attr = in_v_attr;\n"
													 "}\n")

			<< glu::TessellationControlSource		("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 "layout (vertices = " + outSizeStr + ") out;\n"
													 "\n"
													 "in highp float in_tc_attr[];\n"
													 "\n"
													 "out highp float in_te_attr[];\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID*" + inSizeStr + "/" + outSizeStr + "];\n"
													 "\n"
													 "	gl_TessLevelInner[0] = 5.0;\n"
													 "	gl_TessLevelInner[1] = 5.0;\n"
													 "\n"
													"	gl_TessLevelOuter[0] = 5.0;\n"
													"	gl_TessLevelOuter[1] = 5.0;\n"
													"	gl_TessLevelOuter[2] = 5.0;\n"
													"	gl_TessLevelOuter[3] = 5.0;\n"
													 "}\n")

			<< glu::TessellationEvaluationSource	("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) +
													 "\n"
													 "in highp float in_te_attr[];\n"
													 "\n"
													 "out mediump vec4 in_f_color;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	highp float x = gl_TessCoord.x*2.0 - 1.0;\n"
													 "	highp float y = gl_TessCoord.y - in_te_attr[int(round(gl_TessCoord.x*float(" + outSizeStr + "-1)))];\n"
													 "	gl_Position = vec4(x, y, 0.0, 1.0);\n"
													 "	in_f_color = vec4(1.0);\n"
													 "}\n")

			<< glu::FragmentSource					("#version 310 es\n"
													 "\n"
													 "layout (location = 0) out mediump vec4 o_color;\n"
													 "\n"
													 "in mediump vec4 in_f_color;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	o_color = in_f_color;\n"
													 "}\n")));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void PatchVertexCountCase::deinit (void)
{
	m_program.clear();
}

PatchVertexCountCase::IterateResult PatchVertexCountCase::iterate (void)
{
	TestLog&					log						= m_testCtx.getLog();
	const RenderContext&		renderCtx				= m_context.getRenderContext();
	const RandomViewport		viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const deUint32				programGL				= m_program->getProgram();
	const glw::Functions&		gl						= renderCtx.getFunctions();

	setViewport(gl, viewport);
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.useProgram(programGL);

	log << TestLog::Message << "Note: input patch size is " << m_inputPatchSize << ", output patch size is " << m_outputPatchSize << TestLog::EndMessage;

	{
		vector<float> attributeData;
		attributeData.reserve(m_inputPatchSize);

		for (int i = 0; i < m_inputPatchSize; i++)
		{
			const float f = (float)i / (float)(m_inputPatchSize-1);
			attributeData.push_back(f*f);
		}

		gl.patchParameteri(GL_PATCH_VERTICES, m_inputPatchSize);
		gl.clear(GL_COLOR_BUFFER_BIT);

		const glu::VertexArrayBinding attrBindings[] =
		{
			glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])
		};

		glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
			glu::pr::Patches(m_inputPatchSize));
		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
	}

	{
		const tcu::Surface			rendered	= getPixels(renderCtx, viewport);
		const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePath);
		const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);

		m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Image comparison failed");
		return STOP;
	}
}

// Test per-patch inputs/outputs.
class PerPatchDataCase : public TestCase
{
public:
	enum CaseType
	{
		CASETYPE_PRIMITIVE_ID_TCS = 0,
		CASETYPE_PRIMITIVE_ID_TES,
		CASETYPE_PATCH_VERTICES_IN_TCS,
		CASETYPE_PATCH_VERTICES_IN_TES,
		CASETYPE_TESS_LEVEL_INNER0_TES,
		CASETYPE_TESS_LEVEL_INNER1_TES,
		CASETYPE_TESS_LEVEL_OUTER0_TES,
		CASETYPE_TESS_LEVEL_OUTER1_TES,
		CASETYPE_TESS_LEVEL_OUTER2_TES,
		CASETYPE_TESS_LEVEL_OUTER3_TES,

		CASETYPE_LAST
	};

	PerPatchDataCase (Context& context, const char* name, const char* description, CaseType caseType, const char* referenceImagePath)
		: TestCase				(context, name, description)
		, m_caseType			(caseType)
		, m_referenceImagePath	(caseTypeUsesRefImageFromFile(caseType) ? referenceImagePath : "")
	{
		DE_ASSERT(caseTypeUsesRefImageFromFile(caseType) == (referenceImagePath != DE_NULL));
	}

	void							init							(void);
	void							deinit							(void);
	IterateResult					iterate							(void);

	static const char*				getCaseTypeName					(CaseType);
	static const char*				getCaseTypeDescription			(CaseType);
	static bool						caseTypeUsesRefImageFromFile	(CaseType);

private:
	static const int				RENDER_SIZE = 256;
	static const int				INPUT_PATCH_SIZE;
	static const int				OUTPUT_PATCH_SIZE;

	const CaseType					m_caseType;
	const string					m_referenceImagePath;

	SharedPtr<const ShaderProgram>	m_program;
};

const int PerPatchDataCase::INPUT_PATCH_SIZE	= 10;
const int PerPatchDataCase::OUTPUT_PATCH_SIZE	= 5;

const char* PerPatchDataCase::getCaseTypeName (CaseType type)
{
	switch (type)
	{
		case CASETYPE_PRIMITIVE_ID_TCS:			return "primitive_id_tcs";
		case CASETYPE_PRIMITIVE_ID_TES:			return "primitive_id_tes";
		case CASETYPE_PATCH_VERTICES_IN_TCS:	return "patch_vertices_in_tcs";
		case CASETYPE_PATCH_VERTICES_IN_TES:	return "patch_vertices_in_tes";
		case CASETYPE_TESS_LEVEL_INNER0_TES:	return "tess_level_inner_0_tes";
		case CASETYPE_TESS_LEVEL_INNER1_TES:	return "tess_level_inner_1_tes";
		case CASETYPE_TESS_LEVEL_OUTER0_TES:	return "tess_level_outer_0_tes";
		case CASETYPE_TESS_LEVEL_OUTER1_TES:	return "tess_level_outer_1_tes";
		case CASETYPE_TESS_LEVEL_OUTER2_TES:	return "tess_level_outer_2_tes";
		case CASETYPE_TESS_LEVEL_OUTER3_TES:	return "tess_level_outer_3_tes";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

const char* PerPatchDataCase::getCaseTypeDescription (CaseType type)
{
	switch (type)
	{
		case CASETYPE_PRIMITIVE_ID_TCS:			return "Read gl_PrimitiveID in TCS and pass it as patch output to TES";
		case CASETYPE_PRIMITIVE_ID_TES:			return "Read gl_PrimitiveID in TES";
		case CASETYPE_PATCH_VERTICES_IN_TCS:	return "Read gl_PatchVerticesIn in TCS and pass it as patch output to TES";
		case CASETYPE_PATCH_VERTICES_IN_TES:	return "Read gl_PatchVerticesIn in TES";
		case CASETYPE_TESS_LEVEL_INNER0_TES:	return "Read gl_TessLevelInner[0] in TES";
		case CASETYPE_TESS_LEVEL_INNER1_TES:	return "Read gl_TessLevelInner[1] in TES";
		case CASETYPE_TESS_LEVEL_OUTER0_TES:	return "Read gl_TessLevelOuter[0] in TES";
		case CASETYPE_TESS_LEVEL_OUTER1_TES:	return "Read gl_TessLevelOuter[1] in TES";
		case CASETYPE_TESS_LEVEL_OUTER2_TES:	return "Read gl_TessLevelOuter[2] in TES";
		case CASETYPE_TESS_LEVEL_OUTER3_TES:	return "Read gl_TessLevelOuter[3] in TES";
		default:
			DE_ASSERT(false);
			return DE_NULL;
	}
}

bool PerPatchDataCase::caseTypeUsesRefImageFromFile (CaseType type)
{
	switch (type)
	{
		case CASETYPE_PRIMITIVE_ID_TCS:
		case CASETYPE_PRIMITIVE_ID_TES:
			return true;

		default:
			return false;
	}
}

void PerPatchDataCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	DE_ASSERT(OUTPUT_PATCH_SIZE < INPUT_PATCH_SIZE);

	const string inSizeStr		= de::toString(INPUT_PATCH_SIZE);
	const string outSizeStr		= de::toString(OUTPUT_PATCH_SIZE);

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
			<< glu::VertexSource					("#version 310 es\n"
													 "\n"
													 "in highp float in_v_attr;\n"
													 "\n"
													 "out highp float in_tc_attr;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	in_tc_attr = in_v_attr;\n"
													 "}\n")

			<< glu::TessellationControlSource		("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 "layout (vertices = " + outSizeStr + ") out;\n"
													 "\n"
													 "in highp float in_tc_attr[];\n"
													 "\n"
													 "out highp float in_te_attr[];\n"
													 + (m_caseType == CASETYPE_PRIMITIVE_ID_TCS			? "patch out mediump int in_te_primitiveIDFromTCS;\n"
													  : m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS	? "patch out mediump int in_te_patchVerticesInFromTCS;\n"
													  : "") +
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID];\n"
													 + (m_caseType == CASETYPE_PRIMITIVE_ID_TCS			? "\tin_te_primitiveIDFromTCS = gl_PrimitiveID;\n"
													  : m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS	? "\tin_te_patchVerticesInFromTCS = gl_PatchVerticesIn;\n"
													  : "") +
													 "\n"
													 "	gl_TessLevelInner[0] = 9.0;\n"
													 "	gl_TessLevelInner[1] = 8.0;\n"
													 "\n"
													"	gl_TessLevelOuter[0] = 7.0;\n"
													"	gl_TessLevelOuter[1] = 6.0;\n"
													"	gl_TessLevelOuter[2] = 5.0;\n"
													"	gl_TessLevelOuter[3] = 4.0;\n"
													 "}\n")

			<< glu::TessellationEvaluationSource	("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) +
													 "\n"
													 "in highp float in_te_attr[];\n"
													 + (m_caseType == CASETYPE_PRIMITIVE_ID_TCS			? "patch in mediump int in_te_primitiveIDFromTCS;\n"
													  : m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS	? "patch in mediump int in_te_patchVerticesInFromTCS;\n"
													  : string()) +
													 "\n"
													 "out mediump vec4 in_f_color;\n"
													 "\n"
													 "uniform highp float u_xScale;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	highp float x = (gl_TessCoord.x*u_xScale + in_te_attr[0]) * 2.0 - 1.0;\n"
													 "	highp float y = gl_TessCoord.y*2.0 - 1.0;\n"
													 "	gl_Position = vec4(x, y, 0.0, 1.0);\n"
													 + (m_caseType == CASETYPE_PRIMITIVE_ID_TCS			? "\tbool ok = in_te_primitiveIDFromTCS == 3;\n"
													  : m_caseType == CASETYPE_PRIMITIVE_ID_TES			? "\tbool ok = gl_PrimitiveID == 3;\n"
													  : m_caseType == CASETYPE_PATCH_VERTICES_IN_TCS	? "\tbool ok = in_te_patchVerticesInFromTCS == " + inSizeStr + ";\n"
													  : m_caseType == CASETYPE_PATCH_VERTICES_IN_TES	? "\tbool ok = gl_PatchVerticesIn == " + outSizeStr + ";\n"
													  : m_caseType == CASETYPE_TESS_LEVEL_INNER0_TES	? "\tbool ok = abs(gl_TessLevelInner[0] - 9.0) < 0.1f;\n"
													  : m_caseType == CASETYPE_TESS_LEVEL_INNER1_TES	? "\tbool ok = abs(gl_TessLevelInner[1] - 8.0) < 0.1f;\n"
													  : m_caseType == CASETYPE_TESS_LEVEL_OUTER0_TES	? "\tbool ok = abs(gl_TessLevelOuter[0] - 7.0) < 0.1f;\n"
													  : m_caseType == CASETYPE_TESS_LEVEL_OUTER1_TES	? "\tbool ok = abs(gl_TessLevelOuter[1] - 6.0) < 0.1f;\n"
													  : m_caseType == CASETYPE_TESS_LEVEL_OUTER2_TES	? "\tbool ok = abs(gl_TessLevelOuter[2] - 5.0) < 0.1f;\n"
													  : m_caseType == CASETYPE_TESS_LEVEL_OUTER3_TES	? "\tbool ok = abs(gl_TessLevelOuter[3] - 4.0) < 0.1f;\n"
													  : DE_NULL) +
													  "	in_f_color = ok ? vec4(1.0) : vec4(vec3(0.0), 1.0);\n"
													 "}\n")

			<< glu::FragmentSource					("#version 310 es\n"
													 "\n"
													 "layout (location = 0) out mediump vec4 o_color;\n"
													 "\n"
													 "in mediump vec4 in_f_color;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	o_color = in_f_color;\n"
													 "}\n")));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void PerPatchDataCase::deinit (void)
{
	m_program.clear();
}

PerPatchDataCase::IterateResult PerPatchDataCase::iterate (void)
{
	TestLog&					log						= m_testCtx.getLog();
	const RenderContext&		renderCtx				= m_context.getRenderContext();
	const RandomViewport		viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const deUint32				programGL				= m_program->getProgram();
	const glw::Functions&		gl						= renderCtx.getFunctions();

	setViewport(gl, viewport);
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.useProgram(programGL);

	log << TestLog::Message << "Note: input patch size is " << INPUT_PATCH_SIZE << ", output patch size is " << OUTPUT_PATCH_SIZE << TestLog::EndMessage;

	{
		const int numPrimitives = m_caseType == CASETYPE_PRIMITIVE_ID_TCS || m_caseType == CASETYPE_PRIMITIVE_ID_TES ? 8 : 1;

		vector<float> attributeData;
		attributeData.reserve(numPrimitives*INPUT_PATCH_SIZE);

		for (int i = 0; i < numPrimitives; i++)
		{
			attributeData.push_back((float)i / (float)numPrimitives);
			for (int j = 0; j < INPUT_PATCH_SIZE-1; j++)
				attributeData.push_back(0.0f);
		}

		gl.patchParameteri(GL_PATCH_VERTICES, INPUT_PATCH_SIZE);
		gl.clear(GL_COLOR_BUFFER_BIT);

		gl.uniform1f(gl.getUniformLocation(programGL, "u_xScale"), 1.0f / (float)numPrimitives);

		const glu::VertexArrayBinding attrBindings[] =
		{
			glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])
		};

		glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
			glu::pr::Patches(numPrimitives*INPUT_PATCH_SIZE));
		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
	}

	{
		const tcu::Surface rendered = getPixels(renderCtx, viewport);

		if (m_caseType == CASETYPE_PRIMITIVE_ID_TCS || m_caseType == CASETYPE_PRIMITIVE_ID_TES)
		{
			DE_ASSERT(caseTypeUsesRefImageFromFile(m_caseType));

			const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePath);
			const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);

			m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Image comparison failed");
			return STOP;
		}
		else
		{
			DE_ASSERT(!caseTypeUsesRefImageFromFile(m_caseType));

			log << TestLog::Image("RenderedImage", "Rendered Image", rendered);

			for (int y = 0; y < rendered.getHeight();	y++)
			for (int x = 0; x < rendered.getWidth();	x++)
			{
				if (rendered.getPixel(x, y) != tcu::RGBA::white())
				{
					log << TestLog::Message << "Failure: expected all white pixels" << TestLog::EndMessage;
					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
					return STOP;
				}
			}

			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
			return STOP;
		}
	}
}

// Basic barrier() usage in TCS.
class BarrierCase : public TestCase
{
public:
	BarrierCase (Context& context, const char* name, const char* description, const char* referenceImagePath)
		: TestCase				(context, name, description)
		, m_referenceImagePath	(referenceImagePath)
	{
	}

	void							init		(void);
	void							deinit		(void);
	IterateResult					iterate		(void);

private:
	static const int				RENDER_SIZE = 256;
	static const int				NUM_VERTICES;

	const string					m_referenceImagePath;

	SharedPtr<const ShaderProgram>	m_program;
};

const int BarrierCase::NUM_VERTICES = 32;

void BarrierCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	const string numVertsStr = de::toString(NUM_VERTICES);

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
			<< glu::VertexSource					("#version 310 es\n"
													 "\n"
													 "in highp float in_v_attr;\n"
													 "\n"
													 "out highp float in_tc_attr;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	in_tc_attr = in_v_attr;\n"
													 "}\n")

			<< glu::TessellationControlSource		("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 "layout (vertices = " + numVertsStr + ") out;\n"
													 "\n"
													 "in highp float in_tc_attr[];\n"
													 "\n"
													 "out highp float in_te_attr[];\n"
													 "patch out highp float in_te_patchAttr;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	in_te_attr[gl_InvocationID] = in_tc_attr[gl_InvocationID];\n"
													 "	in_te_patchAttr = 0.0f;\n"
													 "	barrier();\n"
													 "	if (gl_InvocationID == 5)\n"
													 "		in_te_patchAttr = float(gl_InvocationID)*0.1;\n"
													 "	barrier();\n"
													 "	highp float temp = in_te_patchAttr + in_te_attr[gl_InvocationID];\n"
													 "	barrier();\n"
													 "	if (gl_InvocationID == " + numVertsStr + "-1)\n"
													 "		in_te_patchAttr = float(gl_InvocationID);\n"
													 "	barrier();\n"
													 "	in_te_attr[gl_InvocationID] = temp;\n"
													 "	barrier();\n"
													 "	temp = temp + in_te_attr[(gl_InvocationID+1) % " + numVertsStr + "];\n"
													 "	barrier();\n"
													 "	in_te_attr[gl_InvocationID] = 0.25*temp;\n"
													 "\n"
													 "	gl_TessLevelInner[0] = 32.0;\n"
													 "	gl_TessLevelInner[1] = 32.0;\n"
													 "\n"
													 "	gl_TessLevelOuter[0] = 32.0;\n"
													 "	gl_TessLevelOuter[1] = 32.0;\n"
													 "	gl_TessLevelOuter[2] = 32.0;\n"
													 "	gl_TessLevelOuter[3] = 32.0;\n"
													 "}\n")

			<< glu::TessellationEvaluationSource	("#version 310 es\n"
													 "#extension GL_EXT_tessellation_shader : require\n"
													 "\n"
													 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_QUADS) +
													 "\n"
													 "in highp float in_te_attr[];\n"
													 "patch in highp float in_te_patchAttr;\n"
													 "\n"
													 "out highp float in_f_blue;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	highp float x = gl_TessCoord.x*2.0 - 1.0;\n"
													 "	highp float y = gl_TessCoord.y - in_te_attr[int(round(gl_TessCoord.x*float(" + numVertsStr + "-1)))];\n"
													 "	gl_Position = vec4(x, y, 0.0, 1.0);\n"
													 "	in_f_blue = abs(in_te_patchAttr - float(" + numVertsStr + "-1));\n"
													 "}\n")

			<< glu::FragmentSource					("#version 310 es\n"
													 "\n"
													 "layout (location = 0) out mediump vec4 o_color;\n"
													 "\n"
													 "in highp float in_f_blue;\n"
													 "\n"
													 "void main (void)\n"
													 "{\n"
													 "	o_color = vec4(1.0, 0.0, in_f_blue, 1.0);\n"
													 "}\n")));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void BarrierCase::deinit (void)
{
	m_program.clear();
}

BarrierCase::IterateResult BarrierCase::iterate (void)
{
	TestLog&					log						= m_testCtx.getLog();
	const RenderContext&		renderCtx				= m_context.getRenderContext();
	const RandomViewport		viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const deUint32				programGL				= m_program->getProgram();
	const glw::Functions&		gl						= renderCtx.getFunctions();

	setViewport(gl, viewport);
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.useProgram(programGL);

	{
		vector<float> attributeData(NUM_VERTICES);

		for (int i = 0; i < NUM_VERTICES; i++)
			attributeData[i] = (float)i / (float)(NUM_VERTICES-1);

		gl.patchParameteri(GL_PATCH_VERTICES, NUM_VERTICES);
		gl.clear(GL_COLOR_BUFFER_BIT);

		const glu::VertexArrayBinding attrBindings[] =
		{
			glu::va::Float("in_v_attr", 1, (int)attributeData.size(), 0, &attributeData[0])
		};

		glu::draw(m_context.getRenderContext(), programGL, DE_LENGTH_OF_ARRAY(attrBindings), &attrBindings[0],
			glu::pr::Patches(NUM_VERTICES));
		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw failed");
	}

	{
		const tcu::Surface			rendered	= getPixels(renderCtx, viewport);
		const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePath);
		const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), rendered.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);

		m_testCtx.setTestResult(success ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, success ? "Pass" : "Image comparison failed");
		return STOP;
	}
}

/*--------------------------------------------------------------------*//*!
 * \brief Base class for testing invariance of entire primitive set
 *
 * Draws two patches with identical tessellation levels and compares the
 * results. Repeats the same with other programs that are only different
 * in irrelevant ways; compares the results between these two programs.
 * Also potentially compares to results produced by different tessellation
 * levels (see e.g. invariance rule #6).
 * Furthermore, repeats the above with multiple different tessellation
 * value sets.
 *
 * The manner of primitive set comparison is defined by subclass. E.g.
 * case for invariance rule #1 tests that same vertices come out, in same
 * order; rule #5 only requires that the same triangles are output, but
 * not necessarily in the same order.
 *//*--------------------------------------------------------------------*/
class PrimitiveSetInvarianceCase : public TestCase
{
public:
	enum WindingUsage
	{
		WINDINGUSAGE_CCW = 0,
		WINDINGUSAGE_CW,
		WINDINGUSAGE_VARY,

		WINDINGUSAGE_LAST
	};

	PrimitiveSetInvarianceCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, bool usePointMode, WindingUsage windingUsage)
		: TestCase			(context, name, description)
		, m_primitiveType	(primType)
		, m_spacing			(spacing)
		, m_usePointMode	(usePointMode)
		, m_windingUsage	(windingUsage)
	{
	}

	void									init				(void);
	void									deinit				(void);
	IterateResult							iterate				(void);

protected:
	struct TessLevels
	{
		float inner[2];
		float outer[4];
		string description (void) const { return tessellationLevelsString(&inner[0], &outer[0]); }
	};
	struct LevelCase
	{
		vector<TessLevels>	levels;
		int					mem; //!< Subclass-defined arbitrary piece of data, for type of the levelcase, if needed. Passed to compare().
		LevelCase (const TessLevels& lev) : levels(vector<TessLevels>(1, lev)), mem(0) {}
		LevelCase (void) : mem(0) {}
	};

	virtual vector<LevelCase>	genTessLevelCases	(void) const;
	virtual bool				compare				(const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, int levelCaseMem) const = 0;

	const TessPrimitiveType		m_primitiveType;

private:
	struct Program
	{
		Winding							winding;
		SharedPtr<const ShaderProgram>	program;

				Program			(Winding w, const SharedPtr<const ShaderProgram>& prog) : winding(w), program(prog) {}

		string	description		(void) const { return string() + "winding mode " + getWindingShaderName(winding); };
	};

	static const int			RENDER_SIZE = 16;

	const SpacingMode			m_spacing;
	const bool					m_usePointMode;
	const WindingUsage			m_windingUsage;

	vector<Program>				m_programs;
};

vector<PrimitiveSetInvarianceCase::LevelCase> PrimitiveSetInvarianceCase::genTessLevelCases (void) const
{
	static const TessLevels basicTessLevelCases[] =
	{
		{ { 1.0f,	1.0f	},	{ 1.0f,		1.0f,	1.0f,	1.0f	} },
		{ { 63.0f,	24.0f	},	{ 15.0f,	42.0f,	10.0f,	12.0f	} },
		{ { 3.0f,	2.0f	},	{ 6.0f,		8.0f,	7.0f,	9.0f	} },
		{ { 4.0f,	6.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
		{ { 2.0f,	2.0f	},	{ 6.0f,		8.0f,	7.0f,	9.0f	} },
		{ { 5.0f,	6.0f	},	{ 1.0f,		1.0f,	1.0f,	1.0f	} },
		{ { 1.0f,	6.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
		{ { 5.0f,	1.0f	},	{ 2.0f,		3.0f,	1.0f,	4.0f	} },
		{ { 5.2f,	1.6f	},	{ 2.9f,		3.4f,	1.5f,	4.1f	} }
	};

	vector<LevelCase> result;
	for (int i = 0; i < DE_LENGTH_OF_ARRAY(basicTessLevelCases); i++)
		result.push_back(LevelCase(basicTessLevelCases[i]));

	{
		de::Random rnd(123);
		for (int i = 0; i < 10; i++)
		{
			TessLevels levels;
			for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.inner); j++)
				levels.inner[j] = rnd.getFloat(1.0f, 16.0f);
			for (int j = 0; j < DE_LENGTH_OF_ARRAY(levels.outer); j++)
				levels.outer[j] = rnd.getFloat(1.0f, 16.0f);
			result.push_back(LevelCase(levels));
		}
	}

	return result;
}

void PrimitiveSetInvarianceCase::init (void)
{
	const int			numDifferentConstantExprCases = 2;
	vector<Winding>		windings;
	switch (m_windingUsage)
	{
		case WINDINGUSAGE_CCW:		windings.push_back(WINDING_CCW); break;
		case WINDINGUSAGE_CW:		windings.push_back(WINDING_CW); break;
		case WINDINGUSAGE_VARY:		windings.push_back(WINDING_CCW);
									windings.push_back(WINDING_CW); break;
		default: DE_ASSERT(false);
	}

	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	for (int constantExprCaseNdx = 0; constantExprCaseNdx < numDifferentConstantExprCases; constantExprCaseNdx++)
	{
		for (int windingCaseNdx = 0; windingCaseNdx < (int)windings.size(); windingCaseNdx++)
		{
			const string	floatLit01 = de::floatToString(10.0f / (float)(constantExprCaseNdx + 10), 2);
			const int		programNdx = (int)m_programs.size();

			m_programs.push_back(Program(windings[windingCaseNdx],
										 SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
					<< glu::VertexSource					("#version 310 es\n"
															 "\n"
															 "in highp float in_v_attr;\n"
															 "out highp float in_tc_attr;\n"
															 "\n"
															 "void main (void)\n"
															 "{\n"
															 "	in_tc_attr = in_v_attr;\n"
															 "}\n")

					<< glu::TessellationControlSource		("#version 310 es\n"
															 "#extension GL_EXT_tessellation_shader : require\n"
															 "\n"
															 "layout (vertices = " + de::toString(constantExprCaseNdx+1) + ") out;\n"
															 "\n"
															 "in highp float in_tc_attr[];\n"
															 "\n"
															 "patch out highp float in_te_positionOffset;\n"
															 "\n"
															 "void main (void)\n"
															 "{\n"
															 "	in_te_positionOffset = in_tc_attr[6];\n"
															 "\n"
															 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
															 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
															 "\n"
															 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
															 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
															 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
															 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
															 "}\n")

					<< glu::TessellationEvaluationSource	("#version 310 es\n"
															 "#extension GL_EXT_tessellation_shader : require\n"
															 "\n"
															 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, windings[windingCaseNdx], m_usePointMode) +
															 "\n"
															 "patch in highp float in_te_positionOffset;\n"
															 "\n"
															 "out highp vec4 in_f_color;\n"
															 "invariant out highp vec3 out_te_tessCoord;\n"
															 "\n"
															 "void main (void)\n"
															 "{\n"
															 "	gl_Position = vec4(gl_TessCoord.xy*" + floatLit01 + " - in_te_positionOffset + float(gl_PrimitiveID)*0.1, 0.0, 1.0);\n"
															 "	in_f_color = vec4(" + floatLit01 + ");\n"
															 "	out_te_tessCoord = gl_TessCoord;\n"
															 "}\n")

					<< glu::FragmentSource					("#version 310 es\n"
															 "\n"
															 "layout (location = 0) out mediump vec4 o_color;\n"
															 "\n"
															 "in highp vec4 in_f_color;\n"
															 "\n"
															 "void main (void)\n"
															 "{\n"
															 "	o_color = in_f_color;\n"
															 "}\n")

					<< glu::TransformFeedbackVarying		("out_te_tessCoord")
					<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)))));

			{
				const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program" + de::toString(programNdx), "Program " + de::toString(programNdx));

				if (programNdx == 0 || !m_programs.back().program->isOk())
					m_testCtx.getLog() << *m_programs.back().program;

				if (!m_programs.back().program->isOk())
					TCU_FAIL("Program compilation failed");

				if (programNdx > 0)
					m_testCtx.getLog() << TestLog::Message << "Note: program " << programNdx << " is similar to above, except some constants are different, and: " << m_programs.back().description() << TestLog::EndMessage;
			}
		}
	}
}

void PrimitiveSetInvarianceCase::deinit (void)
{
	m_programs.clear();
}

PrimitiveSetInvarianceCase::IterateResult PrimitiveSetInvarianceCase::iterate (void)
{
	typedef TransformFeedbackHandler<Vec3> TFHandler;

	TestLog&					log					= m_testCtx.getLog();
	const RenderContext&		renderCtx			= m_context.getRenderContext();
	const RandomViewport		viewport			(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const glw::Functions&		gl					= renderCtx.getFunctions();
	const vector<LevelCase>		tessLevelCases		= genTessLevelCases();
	vector<vector<int> >		primitiveCounts;
	int							maxNumPrimitives	= -1;

	for (int caseNdx = 0; caseNdx < (int)tessLevelCases.size(); caseNdx++)
	{
		primitiveCounts.push_back(vector<int>());
		for (int i = 0; i < (int)tessLevelCases[caseNdx].levels.size(); i++)
		{
			const int primCount = referencePrimitiveCount(m_primitiveType, m_spacing, m_usePointMode,
														  &tessLevelCases[caseNdx].levels[i].inner[0], &tessLevelCases[caseNdx].levels[i].outer[0]);
			primitiveCounts.back().push_back(primCount);
			maxNumPrimitives = de::max(maxNumPrimitives, primCount);
		}
	}

	const deUint32				primitiveTypeGL		= outputPrimitiveTypeGL(m_primitiveType, m_usePointMode);
	const TFHandler				transformFeedback	(m_context.getRenderContext(), 2*maxNumPrimitives*numVerticesPerPrimitive(primitiveTypeGL));

	setViewport(gl, viewport);
	gl.patchParameteri(GL_PATCH_VERTICES, 7);

	for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < (int)tessLevelCases.size(); tessLevelCaseNdx++)
	{
		const LevelCase&	levelCase = tessLevelCases[tessLevelCaseNdx];
		vector<Vec3>		firstPrimVertices;

		{
			string tessLevelsStr;
			for (int i = 0; i < (int)levelCase.levels.size(); i++)
				tessLevelsStr += (levelCase.levels.size() > 1 ? "\n" : "") + levelCase.levels[i].description();
			log << TestLog::Message << "Tessellation level sets: " << tessLevelsStr << TestLog::EndMessage;
		}

		for (int subTessLevelCaseNdx = 0; subTessLevelCaseNdx < (int)levelCase.levels.size(); subTessLevelCaseNdx++)
		{
			const TessLevels&				tessLevels		= levelCase.levels[subTessLevelCaseNdx];
			const float						(&inner)[2]		= tessLevels.inner;
			const float						(&outer)[4]		= tessLevels.outer;
			const float						attribute[2*7]	= { inner[0], inner[1], outer[0], outer[1], outer[2], outer[3], 0.0f,
																inner[0], inner[1], outer[0], outer[1], outer[2], outer[3], 0.5f };
			const glu::VertexArrayBinding	bindings[]		= { glu::va::Float("in_v_attr", 1, DE_LENGTH_OF_ARRAY(attribute), 0, &attribute[0]) };

			for (int programNdx = 0; programNdx < (int)m_programs.size(); programNdx++)
			{
				const deUint32				programGL	= m_programs[programNdx].program->getProgram();
				gl.useProgram(programGL);
				const TFHandler::Result		tfResult	= transformFeedback.renderAndGetPrimitives(programGL, primitiveTypeGL, DE_LENGTH_OF_ARRAY(bindings), &bindings[0], DE_LENGTH_OF_ARRAY(attribute));

				if (tfResult.numPrimitives != 2*primitiveCounts[tessLevelCaseNdx][subTessLevelCaseNdx])
				{
					log << TestLog::Message << "Failure: GL reported GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN to be "
											<< tfResult.numPrimitives << ", reference value is " << 2*primitiveCounts[tessLevelCaseNdx][subTessLevelCaseNdx]
											<< TestLog::EndMessage;

					m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives");
					return STOP;
				}

				{
					const int			half			= (int)tfResult.varying.size()/2;
					const vector<Vec3>	prim0Vertices	= vector<Vec3>(tfResult.varying.begin(), tfResult.varying.begin() + half);
					const Vec3* const	prim1Vertices	= &tfResult.varying[half];

					for (int vtxNdx = 0; vtxNdx < (int)prim0Vertices.size(); vtxNdx++)
					{
						if (prim0Vertices[vtxNdx] != prim1Vertices[vtxNdx])
						{
							log << TestLog::Message << "Failure: tessellation coordinate at index " << vtxNdx << " differs between two primitives drawn in one draw call" << TestLog::EndMessage
								<< TestLog::Message << "Note: the coordinate is " << prim0Vertices[vtxNdx] << " for the first primitive and " << prim1Vertices[vtxNdx] << " for the second" << TestLog::EndMessage
								<< TestLog::Message << "Note: tessellation levels for both primitives were: " << tessellationLevelsString(&inner[0], &outer[0]) << TestLog::EndMessage;
							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives");
							return STOP;
						}
					}

					if (programNdx == 0 && subTessLevelCaseNdx == 0)
						firstPrimVertices = prim0Vertices;
					else
					{
						const bool compareOk = compare(firstPrimVertices, prim0Vertices, levelCase.mem);
						if (!compareOk)
						{
							log << TestLog::Message << "Note: comparison of tessellation coordinates failed; comparison was made between following cases:\n"
													<< "  - case A: program 0, tessellation levels: " << tessLevelCases[tessLevelCaseNdx].levels[0].description() << "\n"
													<< "  - case B: program " << programNdx << ", tessellation levels: " << tessLevels.description() << TestLog::EndMessage;
							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of primitives");
							return STOP;
						}
					}
				}
			}
		}
	}

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #1
 *
 * Test that the sequence of primitives input to the TES only depends on
 * the tessellation levels, tessellation mode, spacing mode, winding, and
 * point mode.
 *//*--------------------------------------------------------------------*/
class InvariantPrimitiveSetCase : public PrimitiveSetInvarianceCase
{
public:
	InvariantPrimitiveSetCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
		: PrimitiveSetInvarianceCase(context, name, description, primType, spacing, usePointMode, winding == WINDING_CCW	? WINDINGUSAGE_CCW
																								: winding == WINDING_CW		? WINDINGUSAGE_CW
																								: WINDINGUSAGE_LAST)
	{
	}

protected:
	virtual bool compare (const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, int) const
	{
		for (int vtxNdx = 0; vtxNdx < (int)coordsA.size(); vtxNdx++)
		{
			if (coordsA[vtxNdx] != coordsB[vtxNdx])
			{
				m_testCtx.getLog() << TestLog::Message << "Failure: tessellation coordinate at index " << vtxNdx << " differs between two programs" << TestLog::EndMessage
								   << TestLog::Message << "Note: the coordinate is " << coordsA[vtxNdx] << " for the first program and " << coordsB[vtxNdx] << " for the other" << TestLog::EndMessage;
				return false;
			}
		}
		return true;
	}
};

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #2
 *
 * Test that the set of vertices along an outer edge of a quad or triangle
 * only depends on that edge's tessellation level, and spacing.
 *
 * For each (outer) edge in the quad or triangle, draw multiple patches
 * with identical tessellation levels for that outer edge but with
 * different values for the other outer edges; compare, among the
 * primitives, the vertices generated for that outer edge. Repeat with
 * different programs, using different winding etc. settings. Compare
 * the edge's vertices between different programs.
 *//*--------------------------------------------------------------------*/
class InvariantOuterEdgeCase : public TestCase
{
public:
	InvariantOuterEdgeCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing)
		: TestCase			(context, name, description)
		, m_primitiveType	(primType)
		, m_spacing			(spacing)
	{
		DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
	}

	void						init		(void);
	void						deinit		(void);
	IterateResult				iterate		(void);

private:
	struct Program
	{
		Winding							winding;
		bool							usePointMode;
		SharedPtr<const ShaderProgram>	program;

				Program			(Winding w, bool point, const SharedPtr<const ShaderProgram>& prog) : winding(w), usePointMode(point), program(prog) {}

		string	description		(void) const { return string() + "winding mode " + getWindingShaderName(winding) + ", " + (usePointMode ? "" : "don't ") + "use point mode"; };
	};

	static vector<float>		generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel);

	static const int			RENDER_SIZE = 16;

	const TessPrimitiveType		m_primitiveType;
	const SpacingMode			m_spacing;

	vector<Program>				m_programs;
};

vector<float> InvariantOuterEdgeCase::generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel)
{
	de::Random rnd(123);
	return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd);
}

void InvariantOuterEdgeCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	for (int windingI = 0; windingI < WINDING_LAST; windingI++)
	{
		const Winding winding = (Winding)windingI;

		for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
		{
			const bool		usePointMode	= usePointModeI != 0;
			const int		programNdx		= (int)m_programs.size();
			const string	floatLit01		= de::floatToString(10.0f / (float)(programNdx + 10), 2);

			m_programs.push_back(Program(winding, usePointMode,
										 SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
				<< glu::VertexSource					("#version 310 es\n"
														 "\n"
														 "in highp float in_v_attr;\n"
														 "out highp float in_tc_attr;\n"
														 "\n"
														 "void main (void)\n"
														 "{\n"
														 "	in_tc_attr = in_v_attr;\n"
														 "}\n")

				<< glu::TessellationControlSource		("#version 310 es\n"
														 "#extension GL_EXT_tessellation_shader : require\n"
														 "\n"
														 "layout (vertices = " + de::toString(programNdx+1) + ") out;\n"
														 "\n"
														 "in highp float in_tc_attr[];\n"
														 "\n"
														 "void main (void)\n"
														 "{\n"
														 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
														 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
														 "\n"
														 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
														 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
														 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
														 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
														 "}\n")

				<< glu::TessellationEvaluationSource	("#version 310 es\n"
														 "#extension GL_EXT_tessellation_shader : require\n"
														 "\n"
														 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, winding, usePointMode) +
														 "\n"
														 "out highp vec4 in_f_color;\n"
														 "invariant out highp vec3 out_te_tessCoord;\n"
														 "\n"
														 "void main (void)\n"
														 "{\n"
														 "	gl_Position = vec4(gl_TessCoord.xy*" + floatLit01 + " - float(gl_PrimitiveID)*0.05, 0.0, 1.0);\n"
														 "	in_f_color = vec4(" + floatLit01 + ");\n"
														 "	out_te_tessCoord = gl_TessCoord;\n"
														 "}\n")

				<< glu::FragmentSource					("#version 310 es\n"
														 "\n"
														 "layout (location = 0) out mediump vec4 o_color;\n"
														 "\n"
														 "in highp vec4 in_f_color;\n"
														 "\n"
														 "void main (void)\n"
														 "{\n"
														 "	o_color = in_f_color;\n"
														 "}\n")

				<< glu::TransformFeedbackVarying		("out_te_tessCoord")
				<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)))));

			{
				const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program" + de::toString(programNdx), "Program " + de::toString(programNdx));

				if (programNdx == 0 || !m_programs.back().program->isOk())
					m_testCtx.getLog() << *m_programs.back().program;

				if (!m_programs.back().program->isOk())
					TCU_FAIL("Program compilation failed");

				if (programNdx > 0)
					m_testCtx.getLog() << TestLog::Message << "Note: program " << programNdx << " is similar to above, except some constants are different, and: " << m_programs.back().description() << TestLog::EndMessage;
			}
		}
	}
}

void InvariantOuterEdgeCase::deinit (void)
{
	m_programs.clear();
}

InvariantOuterEdgeCase::IterateResult InvariantOuterEdgeCase::iterate (void)
{
	typedef TransformFeedbackHandler<Vec3> TFHandler;

	TestLog&							log							= m_testCtx.getLog();
	const RenderContext&				renderCtx					= m_context.getRenderContext();
	const RandomViewport				viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const glw::Functions&				gl							= renderCtx.getFunctions();

	static const float					singleOuterEdgeLevels[]		= { 1.0f, 1.2f, 1.9f, 2.3f, 2.8f, 3.3f, 3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f };
	const int							numPatchesPerDrawCall		= 10;
	const vector<OuterEdgeDescription>	edgeDescriptions			= outerEdgeDescriptions(m_primitiveType);

	{
		// Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
		int maxNumVerticesInDrawCall = 0;
		{
			const vector<float> patchTessLevels = generatePatchTessLevels(numPatchesPerDrawCall, 0 /* outer-edge index doesn't affect vertex count */, arrayMax(singleOuterEdgeLevels));

			for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
				maxNumVerticesInDrawCall = de::max(maxNumVerticesInDrawCall,
												   multiplePatchReferenceVertexCount(m_primitiveType, m_spacing, usePointModeI != 0, &patchTessLevels[0], numPatchesPerDrawCall));
		}

		{
			const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);

			setViewport(gl, viewport);
			gl.patchParameteri(GL_PATCH_VERTICES, 6);

			for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++)
			{
				const OuterEdgeDescription& edgeDesc = edgeDescriptions[outerEdgeIndex];

				for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels); outerEdgeLevelCaseNdx++)
				{
					typedef std::set<Vec3, VecLexLessThan<3> > Vec3Set;

					const vector<float>				patchTessLevels		= generatePatchTessLevels(numPatchesPerDrawCall, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
					const glu::VertexArrayBinding	bindings[]			= { glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0]) };
					Vec3Set							firstOuterEdgeVertices; // Vertices of the outer edge of the first patch of the first program's draw call; used for comparison with other patches.

					log << TestLog::Message << "Testing with outer tessellation level " << singleOuterEdgeLevels[outerEdgeLevelCaseNdx]
											<< " for the " << edgeDesc.description() << " edge, and with various levels for other edges, and with all programs" << TestLog::EndMessage;

					for (int programNdx = 0; programNdx < (int)m_programs.size(); programNdx++)
					{
						const Program& program		= m_programs[programNdx];
						const deUint32 programGL	= program.program->getProgram();

						gl.useProgram(programGL);

						{
							const TFHandler::Result		tfResult			= tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, program.usePointMode),
																											   DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size());
							const int					refNumVertices		= multiplePatchReferenceVertexCount(m_primitiveType, m_spacing, program.usePointMode, &patchTessLevels[0], numPatchesPerDrawCall);
							int							numVerticesRead		= 0;

							if ((int)tfResult.varying.size() != refNumVertices)
							{
								log << TestLog::Message << "Failure: the number of vertices returned by transform feedback is "
														<< tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage
									<< TestLog::Message << "Note: rendered " << numPatchesPerDrawCall
														<< " patches in one draw call; tessellation levels for each patch are (in order [inner0, inner1, outer0, outer1, outer2, outer3]):\n"
														<< containerStr(patchTessLevels, 6) << TestLog::EndMessage;

								m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
								return STOP;
							}

							// Check the vertices of each patch.

							for (int patchNdx = 0; patchNdx < numPatchesPerDrawCall; patchNdx++)
							{
								const float* const	innerLevels			= &patchTessLevels[6*patchNdx + 0];
								const float* const	outerLevels			= &patchTessLevels[6*patchNdx + 2];
								const int			patchNumVertices	= referenceVertexCount(m_primitiveType, m_spacing, program.usePointMode, innerLevels, outerLevels);
								Vec3Set				outerEdgeVertices;

								// We're interested in just the vertices on the current outer edge.
								for(int vtxNdx = numVerticesRead; vtxNdx < numVerticesRead + patchNumVertices; vtxNdx++)
								{
									const Vec3& vtx = tfResult.varying[vtxNdx];
									if (edgeDesc.contains(vtx))
										outerEdgeVertices.insert(tfResult.varying[vtxNdx]);
								}

								// Check that the outer edge contains an appropriate number of vertices.
								{
									const int refNumVerticesOnOuterEdge = 1 + getClampedRoundedTessLevel(m_spacing, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);

									if ((int)outerEdgeVertices.size() != refNumVerticesOnOuterEdge)
									{
										log << TestLog::Message << "Failure: the number of vertices on outer edge is " << outerEdgeVertices.size()
																<< ", expected " << refNumVerticesOnOuterEdge << TestLog::EndMessage
											<< TestLog::Message << "Note: vertices on the outer edge are:\n" << containerStr(outerEdgeVertices, 5, 0) << TestLog::EndMessage
											<< TestLog::Message << "Note: the following parameters were used: " << program.description()
																<< ", tessellation levels: " << tessellationLevelsString(innerLevels, outerLevels) << TestLog::EndMessage;
										m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
										return STOP;
									}
								}

								// Compare the vertices to those of the first patch (unless this is the first patch).

								if (programNdx == 0 && patchNdx == 0)
									firstOuterEdgeVertices = outerEdgeVertices;
								else
								{
									if (firstOuterEdgeVertices != outerEdgeVertices)
									{
										log << TestLog::Message << "Failure: vertices generated for the edge differ between the following cases:\n"
																<< "  - case A: " << m_programs[0].description() << ", tessellation levels: "
																				  << tessellationLevelsString(&patchTessLevels[0], &patchTessLevels[2]) << "\n"
																<< "  - case B: " << program.description() << ", tessellation levels: "
																				  << tessellationLevelsString(innerLevels, outerLevels) << TestLog::EndMessage;

										log << TestLog::Message << "Note: resulting vertices for the edge for the cases were:\n"
																<< "  - case A: " << containerStr(firstOuterEdgeVertices, 5, 14) << "\n"
																<< "  - case B: " << containerStr(outerEdgeVertices, 5, 14) << TestLog::EndMessage;

										m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
										return STOP;
									}
								}

								numVerticesRead += patchNumVertices;
							}

							DE_ASSERT(numVerticesRead == (int)tfResult.varying.size());
						}
					}
				}
			}
		}
	}

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #3
 *
 * Test that the vertices along an outer edge are placed symmetrically.
 *
 * Draw multiple patches with different tessellation levels and different
 * point_mode, winding etc. Before outputting tesscoords with TF, mirror
 * the vertices in the TES such that every vertex on an outer edge -
 * except the possible middle vertex - should be duplicated in the output.
 * Check that appropriate duplicates exist.
 *//*--------------------------------------------------------------------*/
class SymmetricOuterEdgeCase : public TestCase
{
public:
	SymmetricOuterEdgeCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
		: TestCase			(context, name, description)
		, m_primitiveType	(primType)
		, m_spacing			(spacing)
		, m_winding			(winding)
		, m_usePointMode	(usePointMode)
	{
	}

	void									init		(void);
	void									deinit		(void);
	IterateResult							iterate		(void);

private:
	static vector<float>					generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel);

	static const int						RENDER_SIZE = 16;

	const TessPrimitiveType					m_primitiveType;
	const SpacingMode						m_spacing;
	const Winding							m_winding;
	const bool								m_usePointMode;

	SharedPtr<const glu::ShaderProgram>		m_program;
};

vector<float> SymmetricOuterEdgeCase::generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel)
{
	de::Random rnd(123);
	return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd);
}

void SymmetricOuterEdgeCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
		<< glu::VertexSource					("#version 310 es\n"
												 "\n"
												 "in highp float in_v_attr;\n"
												 "out highp float in_tc_attr;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	in_tc_attr = in_v_attr;\n"
												 "}\n")

		<< glu::TessellationControlSource		("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 "layout (vertices = 1) out;\n"
												 "\n"
												 "in highp float in_tc_attr[];\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
												 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
												 "\n"
												 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
												 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
												 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
												 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
												 "}\n")

		<< glu::TessellationEvaluationSource	("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
												 "\n"
												 "out highp vec4 in_f_color;\n"
												 "out highp vec4 out_te_tessCoord_isMirrored;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
													"	float x = gl_TessCoord.x;\n"
													"	float y = gl_TessCoord.y;\n"
													"	float z = gl_TessCoord.z;\n"
													"	// Mirror one half of each outer edge onto the other half, except the endpoints (because they belong to two edges)\n"
													"	out_te_tessCoord_isMirrored = z == 0.0 && x > 0.5 && x != 1.0 ? vec4(1.0-x,  1.0-y,    0.0, 1.0)\n"
													"	                            : y == 0.0 && z > 0.5 && z != 1.0 ? vec4(1.0-x,    0.0,  1.0-z, 1.0)\n"
													"	                            : x == 0.0 && y > 0.5 && y != 1.0 ? vec4(  0.0,  1.0-y,  1.0-z, 1.0)\n"
													"	                            : vec4(x, y, z, 0.0);\n"
												  : m_primitiveType == TESSPRIMITIVETYPE_QUADS ?
													"	float x = gl_TessCoord.x;\n"
													"	float y = gl_TessCoord.y;\n"
													"	// Mirror one half of each outer edge onto the other half, except the endpoints (because they belong to two edges)\n"
													"	out_te_tessCoord_isMirrored = (x == 0.0 || x == 1.0) && y > 0.5 && y != 1.0 ? vec4(    x, 1.0-y, 0.0, 1.0)\n"
													"	                            : (y == 0.0 || y == 1.0) && x > 0.5 && x != 1.0 ? vec4(1.0-x,     y, 0.0, 1.0)\n"
													"	                            : vec4(x, y, 0.0, 0.0);\n"
												  : m_primitiveType == TESSPRIMITIVETYPE_ISOLINES ?
													"	float x = gl_TessCoord.x;\n"
													"	float y = gl_TessCoord.y;\n"
													"	// Mirror one half of each outer edge onto the other half\n"
													"	out_te_tessCoord_isMirrored = (x == 0.0 || x == 1.0) && y > 0.5 ? vec4(x, 1.0-y, 0.0, 1.0)\n"
													"	                            : vec4(x, y, 0.0, 0.0f);\n"
												  : DE_NULL) +
												 "\n"
												 "	gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n"
												 "	in_f_color = vec4(1.0);\n"
												 "}\n")

		<< glu::FragmentSource					("#version 310 es\n"
												 "\n"
												 "layout (location = 0) out mediump vec4 o_color;\n"
												 "\n"
												 "in highp vec4 in_f_color;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	o_color = in_f_color;\n"
												 "}\n")

		<< glu::TransformFeedbackVarying		("out_te_tessCoord_isMirrored")
		<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void SymmetricOuterEdgeCase::deinit (void)
{
	m_program.clear();
}

SymmetricOuterEdgeCase::IterateResult SymmetricOuterEdgeCase::iterate (void)
{
	typedef TransformFeedbackHandler<Vec4> TFHandler;

	TestLog&							log							= m_testCtx.getLog();
	const RenderContext&				renderCtx					= m_context.getRenderContext();
	const RandomViewport				viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const glw::Functions&				gl							= renderCtx.getFunctions();

	static const float					singleOuterEdgeLevels[]		= { 1.0f, 1.2f, 1.9f, 2.3f, 2.8f, 3.3f, 3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f };
	const vector<OuterEdgeDescription>	edgeDescriptions			= outerEdgeDescriptions(m_primitiveType);

	{
		// Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
		int maxNumVerticesInDrawCall;
		{
			const vector<float> patchTessLevels = generatePatchTessLevels(1, 0 /* outer-edge index doesn't affect vertex count */, arrayMax(singleOuterEdgeLevels));
			maxNumVerticesInDrawCall = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &patchTessLevels[0], &patchTessLevels[2]);
		}

		{
			const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);

			setViewport(gl, viewport);
			gl.patchParameteri(GL_PATCH_VERTICES, 6);

			for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++)
			{
				const OuterEdgeDescription& edgeDesc = edgeDescriptions[outerEdgeIndex];

				for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels); outerEdgeLevelCaseNdx++)
				{
					typedef std::set<Vec3, VecLexLessThan<3> > Vec3Set;

					const vector<float>				patchTessLevels		= generatePatchTessLevels(1, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
					const glu::VertexArrayBinding	bindings[]			= { glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0]) };

					log << TestLog::Message << "Testing with outer tessellation level " << singleOuterEdgeLevels[outerEdgeLevelCaseNdx]
											<< " for the " << edgeDesc.description() << " edge, and with various levels for other edges" << TestLog::EndMessage;

					{
						const deUint32 programGL = m_program->getProgram();

						gl.useProgram(programGL);

						{
							const TFHandler::Result		tfResult		= tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
																										   DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size());
							const int					refNumVertices	= referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &patchTessLevels[0], &patchTessLevels[2]);

							if ((int)tfResult.varying.size() != refNumVertices)
							{
								log << TestLog::Message << "Failure: the number of vertices returned by transform feedback is "
														<< tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage
									<< TestLog::Message << "Note: rendered 1 patch, tessellation levels are (in order [inner0, inner1, outer0, outer1, outer2, outer3]):\n"
														<< containerStr(patchTessLevels, 6) << TestLog::EndMessage;

								m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
								return STOP;
							}

							// Check the vertices.

							{
								Vec3Set nonMirroredEdgeVertices;
								Vec3Set mirroredEdgeVertices;

								// We're interested in just the vertices on the current outer edge.
								for(int vtxNdx = 0; vtxNdx < refNumVertices; vtxNdx++)
								{
									const Vec3& vtx = tfResult.varying[vtxNdx].swizzle(0,1,2);
									if (edgeDesc.contains(vtx))
									{
										// Ignore the middle vertex of the outer edge, as it's exactly at the mirroring point;
										// for isolines, also ignore (0, 0) and (1, 0) because there's no mirrored counterpart for them.
										if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES && vtx == tcu::select(Vec3(0.0f), Vec3(0.5f), singleTrueMask<3>(edgeDesc.constantCoordinateIndex)))
											continue;
										if (m_primitiveType == TESSPRIMITIVETYPE_QUADS && vtx.swizzle(0,1) == tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]),
																															   Vec2(0.5f),
																															   singleTrueMask<2>(edgeDesc.constantCoordinateIndex)))
											continue;
										if (m_primitiveType == TESSPRIMITIVETYPE_ISOLINES && (vtx == Vec3(0.0f, 0.5f, 0.0f) || vtx == Vec3(1.0f, 0.5f, 0.0f) ||
																							  vtx == Vec3(0.0f, 0.0f, 0.0f) || vtx == Vec3(1.0f, 0.0f, 0.0f)))
											continue;

										const bool isMirrored = tfResult.varying[vtxNdx].w() > 0.5f;
										if (isMirrored)
											mirroredEdgeVertices.insert(vtx);
										else
											nonMirroredEdgeVertices.insert(vtx);
									}
								}

								if (m_primitiveType != TESSPRIMITIVETYPE_ISOLINES)
								{
									// Check that both endpoints are present. Note that endpoints aren't mirrored by the shader, since they belong to more than one edge.

									Vec3 endpointA;
									Vec3 endpointB;

									if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
									{
										endpointA = tcu::select(Vec3(1.0f), Vec3(0.0f), singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 1) % 3));
										endpointB = tcu::select(Vec3(1.0f), Vec3(0.0f), singleTrueMask<3>((edgeDesc.constantCoordinateIndex + 2) % 3));
									}
									else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
									{
										endpointA.xy() = tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]), Vec2(0.0f), singleTrueMask<2>(edgeDesc.constantCoordinateIndex));
										endpointB.xy() = tcu::select(Vec2(edgeDesc.constantCoordinateValueChoices[0]), Vec2(1.0f), singleTrueMask<2>(edgeDesc.constantCoordinateIndex));
									}
									else
										DE_ASSERT(false);

									if (!contains(nonMirroredEdgeVertices, endpointA) ||
										!contains(nonMirroredEdgeVertices, endpointB))
									{
										log << TestLog::Message << "Failure: edge doesn't contain both endpoints, " << endpointA << " and " << endpointB << TestLog::EndMessage
											<< TestLog::Message << "Note: non-mirrored vertices:\n" << containerStr(nonMirroredEdgeVertices, 5)
																<< "\nmirrored vertices:\n" << containerStr(mirroredEdgeVertices, 5) << TestLog::EndMessage;
										m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
										return STOP;
									}
									nonMirroredEdgeVertices.erase(endpointA);
									nonMirroredEdgeVertices.erase(endpointB);
								}

								if (nonMirroredEdgeVertices != mirroredEdgeVertices)
								{
									log << TestLog::Message << "Failure: the set of mirrored edges isn't equal to the set of non-mirrored edges (ignoring endpoints and possible middle)" << TestLog::EndMessage
										<< TestLog::Message << "Note: non-mirrored vertices:\n" << containerStr(nonMirroredEdgeVertices, 5)
																<< "\nmirrored vertices:\n" << containerStr(mirroredEdgeVertices, 5) << TestLog::EndMessage;
									m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
									return STOP;
								}
							}
						}
					}
				}
			}
		}
	}

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #4
 *
 * Test that the vertices on an outer edge don't depend on which of the
 * edges it is, other than with respect to component order.
 *//*--------------------------------------------------------------------*/
class OuterEdgeVertexSetIndexIndependenceCase : public TestCase
{
public:
	OuterEdgeVertexSetIndexIndependenceCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
		: TestCase			(context, name, description)
		, m_primitiveType	(primType)
		, m_spacing			(spacing)
		, m_winding			(winding)
		, m_usePointMode	(usePointMode)
	{
		DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
	}

	void									init		(void);
	void									deinit		(void);
	IterateResult							iterate		(void);

private:
	static vector<float>					generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel);

	static const int						RENDER_SIZE = 16;

	const TessPrimitiveType					m_primitiveType;
	const SpacingMode						m_spacing;
	const Winding							m_winding;
	const bool								m_usePointMode;

	SharedPtr<const glu::ShaderProgram>		m_program;
};

vector<float> OuterEdgeVertexSetIndexIndependenceCase::generatePatchTessLevels (int numPatches, int constantOuterLevelIndex, float constantOuterLevel)
{
	de::Random rnd(123);
	return generateRandomPatchTessLevels(numPatches, constantOuterLevelIndex, constantOuterLevel, rnd);
}

void OuterEdgeVertexSetIndexIndependenceCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
		<< glu::VertexSource					("#version 310 es\n"
												 "\n"
												 "in highp float in_v_attr;\n"
												 "out highp float in_tc_attr;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	in_tc_attr = in_v_attr;\n"
												 "}\n")

		<< glu::TessellationControlSource		("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 "layout (vertices = 1) out;\n"
												 "\n"
												 "in highp float in_tc_attr[];\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
												 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
												 "\n"
												 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
												 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
												 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
												 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
												 "}\n")

		<< glu::TessellationEvaluationSource	("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
												 "\n"
												 "out highp vec4 in_f_color;\n"
												 "out highp vec3 out_te_tessCoord;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	out_te_tessCoord = gl_TessCoord;"
												 "	gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n"
												 "	in_f_color = vec4(1.0);\n"
												 "}\n")

		<< glu::FragmentSource					("#version 310 es\n"
												 "\n"
												 "layout (location = 0) out mediump vec4 o_color;\n"
												 "\n"
												 "in highp vec4 in_f_color;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	o_color = in_f_color;\n"
												 "}\n")

		<< glu::TransformFeedbackVarying		("out_te_tessCoord")
		<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void OuterEdgeVertexSetIndexIndependenceCase::deinit (void)
{
	m_program.clear();
}

OuterEdgeVertexSetIndexIndependenceCase::IterateResult OuterEdgeVertexSetIndexIndependenceCase::iterate (void)
{
	typedef TransformFeedbackHandler<Vec3> TFHandler;

	TestLog&							log							= m_testCtx.getLog();
	const RenderContext&				renderCtx					= m_context.getRenderContext();
	const RandomViewport				viewport					(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const glw::Functions&				gl							= renderCtx.getFunctions();
	const deUint32						programGL					= m_program->getProgram();

	static const float					singleOuterEdgeLevels[]		= { 1.0f, 1.2f, 1.9f, 2.3f, 2.8f, 3.3f, 3.8f, 10.2f, 1.6f, 24.4f, 24.7f, 63.0f };
	const vector<OuterEdgeDescription>	edgeDescriptions			= outerEdgeDescriptions(m_primitiveType);

	gl.useProgram(programGL);
	setViewport(gl, viewport);
	gl.patchParameteri(GL_PATCH_VERTICES, 6);

	{
		// Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
		int maxNumVerticesInDrawCall = 0;
		{
			const vector<float> patchTessLevels = generatePatchTessLevels(1, 0 /* outer-edge index doesn't affect vertex count */, arrayMax(singleOuterEdgeLevels));
			maxNumVerticesInDrawCall = referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &patchTessLevels[0], &patchTessLevels[2]);
		}

		{
			const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);

			for (int outerEdgeLevelCaseNdx = 0; outerEdgeLevelCaseNdx < DE_LENGTH_OF_ARRAY(singleOuterEdgeLevels); outerEdgeLevelCaseNdx++)
			{
				typedef std::set<Vec3, VecLexLessThan<3> > Vec3Set;

				Vec3Set firstEdgeVertices;

				for (int outerEdgeIndex = 0; outerEdgeIndex < (int)edgeDescriptions.size(); outerEdgeIndex++)
				{
					const OuterEdgeDescription&		edgeDesc			= edgeDescriptions[outerEdgeIndex];
					const vector<float>				patchTessLevels		= generatePatchTessLevels(1, outerEdgeIndex, singleOuterEdgeLevels[outerEdgeLevelCaseNdx]);
					const glu::VertexArrayBinding	bindings[]			= { glu::va::Float("in_v_attr", 1, (int)patchTessLevels.size(), 0, &patchTessLevels[0]) };

					log << TestLog::Message << "Testing with outer tessellation level " << singleOuterEdgeLevels[outerEdgeLevelCaseNdx]
											<< " for the " << edgeDesc.description() << " edge, and with various levels for other edges" << TestLog::EndMessage;

					{
						const TFHandler::Result		tfResult		= tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
																										DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)patchTessLevels.size());
						const int					refNumVertices	= referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &patchTessLevels[0], &patchTessLevels[2]);

						if ((int)tfResult.varying.size() != refNumVertices)
						{
							log << TestLog::Message << "Failure: the number of vertices returned by transform feedback is "
													<< tfResult.varying.size() << ", expected " << refNumVertices << TestLog::EndMessage
								<< TestLog::Message << "Note: rendered 1 patch, tessellation levels are (in order [inner0, inner1, outer0, outer1, outer2, outer3]):\n"
													<< containerStr(patchTessLevels, 6) << TestLog::EndMessage;

							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
							return STOP;
						}

						{
							Vec3Set currentEdgeVertices;

							// Get the vertices on the current outer edge.
							for(int vtxNdx = 0; vtxNdx < refNumVertices; vtxNdx++)
							{
								const Vec3& vtx = tfResult.varying[vtxNdx];
								if (edgeDesc.contains(vtx))
								{
									// Swizzle components to match the order of the first edge.
									if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
									{
										currentEdgeVertices.insert(outerEdgeIndex == 0 ? vtx
																	: outerEdgeIndex == 1 ? vtx.swizzle(1, 0, 2)
																	: outerEdgeIndex == 2 ? vtx.swizzle(2, 1, 0)
																	: Vec3(-1.0f));
									}
									else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
									{
										currentEdgeVertices.insert(Vec3(outerEdgeIndex == 0 ? vtx.y()
																		: outerEdgeIndex == 1 ? vtx.x()
																		: outerEdgeIndex == 2 ? vtx.y()
																		: outerEdgeIndex == 3 ? vtx.x()
																		: -1.0f,
																		0.0f, 0.0f));
									}
									else
										DE_ASSERT(false);
								}
							}

							if (outerEdgeIndex == 0)
								firstEdgeVertices = currentEdgeVertices;
							else
							{
								// Compare vertices of this edge to those of the first edge.

								if (currentEdgeVertices != firstEdgeVertices)
								{
									const char* const swizzleDesc = m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? (outerEdgeIndex == 1 ? "(y, x, z)"
																													: outerEdgeIndex == 2 ? "(z, y, x)"
																													: DE_NULL)
																	: m_primitiveType == TESSPRIMITIVETYPE_QUADS ? (outerEdgeIndex == 1 ? "(x, 0)"
																												: outerEdgeIndex == 2 ? "(y, 0)"
																												: outerEdgeIndex == 3 ? "(x, 0)"
																												: DE_NULL)
																	: DE_NULL;

									log << TestLog::Message << "Failure: the set of vertices on the " << edgeDesc.description() << " edge"
															<< " doesn't match the set of vertices on the " << edgeDescriptions[0].description() << " edge" << TestLog::EndMessage
										<< TestLog::Message << "Note: set of vertices on " << edgeDesc.description() << " edge, components swizzled like " << swizzleDesc
															<< " to match component order on first edge:\n" << containerStr(currentEdgeVertices, 5)
															<< "\non " << edgeDescriptions[0].description() << " edge:\n" << containerStr(firstEdgeVertices, 5) << TestLog::EndMessage;
									m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid set of vertices");
									return STOP;
								}
							}
						}
					}
				}
			}
		}
	}

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #5
 *
 * Test that the set of triangles input to the TES only depends on the
 * tessellation levels, tessellation mode and spacing mode. Specifically,
 * winding doesn't change the set of triangles, though it can change the
 * order in which they are input to TES, and can (and will) change the
 * vertex order within a triangle.
 *//*--------------------------------------------------------------------*/
class InvariantTriangleSetCase : public PrimitiveSetInvarianceCase
{
public:
	InvariantTriangleSetCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing)
		: PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY)
	{
		DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
	}

protected:
	virtual bool compare (const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, int) const
	{
		return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog());
	}
};

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #6
 *
 * Test that the set of inner triangles input to the TES only depends on
 * the inner tessellation levels, tessellation mode and spacing mode.
 *//*--------------------------------------------------------------------*/
class InvariantInnerTriangleSetCase : public PrimitiveSetInvarianceCase
{
public:
	InvariantInnerTriangleSetCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing)
		: PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY)
	{
		DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
	}

protected:
	virtual vector<LevelCase> genTessLevelCases (void) const
	{
		const int					numSubCases		= 4;
		const vector<LevelCase>		baseResults		= PrimitiveSetInvarianceCase::genTessLevelCases();
		vector<LevelCase>			result;
		de::Random					rnd				(123);

		// Generate variants with different values for irrelevant levels.
		for (int baseNdx = 0; baseNdx < (int)baseResults.size(); baseNdx++)
		{
			const TessLevels&	base	= baseResults[baseNdx].levels[0];
			TessLevels			levels	= base;
			LevelCase			levelCase;

			for (int subNdx = 0; subNdx < numSubCases; subNdx++)
			{
				levelCase.levels.push_back(levels);

				for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); i++)
					levels.outer[i] = rnd.getFloat(2.0f, 16.0f);
				if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
					levels.inner[1] = rnd.getFloat(2.0f, 16.0f);
			}

			result.push_back(levelCase);
		}

		return result;
	}

	struct IsInnerTriangleTriangle
	{
		bool operator() (const Vec3* vertices) const
		{
			for (int v = 0; v < 3; v++)
				for (int c = 0; c < 3; c++)
					if (vertices[v][c] == 0.0f)
						return false;
			return true;
		}
	};

	struct IsInnerQuadTriangle
	{
		bool operator() (const Vec3* vertices) const
		{
			for (int v = 0; v < 3; v++)
				for (int c = 0; c < 2; c++)
					if (vertices[v][c] == 0.0f || vertices[v][c] == 1.0f)
						return false;
			return true;
		}
	};

	virtual bool compare (const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, int) const
	{
		if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
			return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(), IsInnerTriangleTriangle(), "outer triangles");
		else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
			return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(), IsInnerQuadTriangle(), "outer triangles");
		else
		{
			DE_ASSERT(false);
			return false;
		}
	}
};

/*--------------------------------------------------------------------*//*!
 * \brief Test invariance rule #7
 *
 * Test that the set of outer triangles input to the TES only depends on
 * tessellation mode, spacing mode and the inner and outer tessellation
 * levels corresponding to the inner and outer edges relevant to that
 * triangle.
 *//*--------------------------------------------------------------------*/
class InvariantOuterTriangleSetCase : public PrimitiveSetInvarianceCase
{
public:
	InvariantOuterTriangleSetCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing)
		: PrimitiveSetInvarianceCase(context, name, description, primType, spacing, false, WINDINGUSAGE_VARY)
	{
		DE_ASSERT(primType == TESSPRIMITIVETYPE_TRIANGLES || primType == TESSPRIMITIVETYPE_QUADS);
	}

protected:
	virtual vector<LevelCase> genTessLevelCases (void) const
	{
		const int					numSubCasesPerEdge	= 4;
		const int					numEdges			= m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES	? 3
														: m_primitiveType == TESSPRIMITIVETYPE_QUADS		? 4
														: -1;
		const vector<LevelCase>		baseResult			= PrimitiveSetInvarianceCase::genTessLevelCases();
		vector<LevelCase>			result;
		de::Random					rnd					(123);

		// Generate variants with different values for irrelevant levels.
		for (int baseNdx = 0; baseNdx < (int)baseResult.size(); baseNdx++)
		{
			const TessLevels& base = baseResult[baseNdx].levels[0];
			if (base.inner[0] == 1.0f || (m_primitiveType == TESSPRIMITIVETYPE_QUADS && base.inner[1] == 1.0f))
				continue;

			for (int edgeNdx = 0; edgeNdx < numEdges; edgeNdx++)
			{
				TessLevels	levels = base;
				LevelCase	levelCase;
				levelCase.mem = edgeNdx;

				for (int subCaseNdx = 0; subCaseNdx < numSubCasesPerEdge; subCaseNdx++)
				{
					levelCase.levels.push_back(levels);

					for (int i = 0; i < DE_LENGTH_OF_ARRAY(levels.outer); i++)
					{
						if (i != edgeNdx)
							levels.outer[i] = rnd.getFloat(2.0f, 16.0f);
					}

					if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
						levels.inner[1] = rnd.getFloat(2.0f, 16.0f);
				}

				result.push_back(levelCase);
			}
		}

		return result;
	}

	class IsTriangleTriangleOnOuterEdge
	{
	public:
		IsTriangleTriangleOnOuterEdge (int edgeNdx) : m_edgeNdx(edgeNdx) {}
		bool operator() (const Vec3* vertices) const
		{
			bool touchesAppropriateEdge = false;
			for (int v = 0; v < 3; v++)
				if (vertices[v][m_edgeNdx] == 0.0f)
					touchesAppropriateEdge = true;

			if (touchesAppropriateEdge)
			{
				const Vec3 avg = (vertices[0] + vertices[1] + vertices[2]) / 3.0f;
				return avg[m_edgeNdx] < avg[(m_edgeNdx+1)%3] &&
					   avg[m_edgeNdx] < avg[(m_edgeNdx+2)%3];
			}
			return false;
		}

	private:
		int m_edgeNdx;
	};

	class IsQuadTriangleOnOuterEdge
	{
	public:
		IsQuadTriangleOnOuterEdge (int edgeNdx) : m_edgeNdx(edgeNdx) {}

		bool onEdge (const Vec3& v) const
		{
			return v[m_edgeNdx%2] == (m_edgeNdx <= 1 ? 0.0f : 1.0f);
		}

		static inline bool onAnyEdge (const Vec3& v)
		{
			return v[0] == 0.0f || v[0] == 1.0f || v[1] == 0.0f || v[1] == 1.0f;
		}

		bool operator() (const Vec3* vertices) const
		{
			for (int v = 0; v < 3; v++)
			{
				const Vec3& a = vertices[v];
				const Vec3& b = vertices[(v+1)%3];
				const Vec3& c = vertices[(v+2)%3];
				if (onEdge(a) && onEdge(b))
					return true;
				if (onEdge(c) && !onAnyEdge(a) && !onAnyEdge(b) && a[m_edgeNdx%2] == b[m_edgeNdx%2])
					return true;
			}

			return false;
		}

	private:
		int m_edgeNdx;
	};

	virtual bool compare (const vector<Vec3>& coordsA, const vector<Vec3>& coordsB, int outerEdgeNdx) const
	{
		if (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES)
		{
			return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(),
									   IsTriangleTriangleOnOuterEdge(outerEdgeNdx),
									   ("inner triangles, and outer triangles corresponding to other edge than edge "
										+ outerEdgeDescriptions(m_primitiveType)[outerEdgeNdx].description()).c_str());
		}
		else if (m_primitiveType == TESSPRIMITIVETYPE_QUADS)
		{
			return compareTriangleSets(coordsA, coordsB, m_testCtx.getLog(),
									   IsQuadTriangleOnOuterEdge(outerEdgeNdx),
									   ("inner triangles, and outer triangles corresponding to other edge than edge "
										+ outerEdgeDescriptions(m_primitiveType)[outerEdgeNdx].description()).c_str());
		}
		else
			DE_ASSERT(false);

		return true;
	}
};

/*--------------------------------------------------------------------*//*!
 * \brief Base class for testing individual components of tess coords
 *
 * Useful for testing parts of invariance rule #8.
 *//*--------------------------------------------------------------------*/
class TessCoordComponentInvarianceCase : public TestCase
{
public:
	TessCoordComponentInvarianceCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
		: TestCase			(context, name, description)
		, m_primitiveType	(primType)
		, m_spacing			(spacing)
		, m_winding			(winding)
		, m_usePointMode	(usePointMode)
	{
	}

	void									init		(void);
	void									deinit		(void);
	IterateResult							iterate		(void);

protected:
	virtual string							tessEvalOutputComponentStatements	(const char* tessCoordComponentName, const char* outputComponentName) const = 0;
	virtual bool							checkTessCoordComponent				(float component) const = 0;

private:
	static vector<float>					genTessLevelCases (int numCases);

	static const int						RENDER_SIZE = 16;

	const TessPrimitiveType					m_primitiveType;
	const SpacingMode						m_spacing;
	const Winding							m_winding;
	const bool								m_usePointMode;

	SharedPtr<const glu::ShaderProgram>		m_program;
};

void TessCoordComponentInvarianceCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
		<< glu::VertexSource					("#version 310 es\n"
												 "\n"
												 "in highp float in_v_attr;\n"
												 "out highp float in_tc_attr;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	in_tc_attr = in_v_attr;\n"
												 "}\n")

		<< glu::TessellationControlSource		("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 "layout (vertices = 1) out;\n"
												 "\n"
												 "in highp float in_tc_attr[];\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
												 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
												 "\n"
												 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
												 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
												 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
												 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
												 "}\n")

		<< glu::TessellationEvaluationSource	("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
												 "\n"
												 "out highp vec4 in_f_color;\n"
												 "out highp vec3 out_te_output;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 + tessEvalOutputComponentStatements("gl_TessCoord.x", "out_te_output.x")
												 + tessEvalOutputComponentStatements("gl_TessCoord.y", "out_te_output.y")

												 + (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ?
													tessEvalOutputComponentStatements("gl_TessCoord.z", "out_te_output.z") :
													"	out_te_output.z = 0.0f;\n") +
												 "	gl_Position = vec4(gl_TessCoord.xy, 0.0, 1.0);\n"
												 "	in_f_color = vec4(1.0);\n"
												 "}\n")

		<< glu::FragmentSource					("#version 310 es\n"
												 "\n"
												 "layout (location = 0) out mediump vec4 o_color;\n"
												 "\n"
												 "in highp vec4 in_f_color;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	o_color = in_f_color;\n"
												 "}\n")

		<< glu::TransformFeedbackVarying		("out_te_output")
		<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void TessCoordComponentInvarianceCase::deinit (void)
{
	m_program.clear();
}

vector<float> TessCoordComponentInvarianceCase::genTessLevelCases (int numCases)
{
	de::Random		rnd(123);
	vector<float>	result;

	for (int i = 0; i < numCases; i++)
		for (int j = 0; j < 6; j++)
			result.push_back(rnd.getFloat(1.0f, 63.0f));

	return result;
}

TessCoordComponentInvarianceCase::IterateResult TessCoordComponentInvarianceCase::iterate (void)
{
	typedef TransformFeedbackHandler<Vec3> TFHandler;

	TestLog&				log					= m_testCtx.getLog();
	const RenderContext&	renderCtx			= m_context.getRenderContext();
	const RandomViewport	viewport			(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const glw::Functions&	gl					= renderCtx.getFunctions();
	const int				numTessLevelCases	= 32;
	const vector<float>		tessLevels			= genTessLevelCases(numTessLevelCases);
	const deUint32			programGL			= m_program->getProgram();

	gl.useProgram(programGL);
	setViewport(gl, viewport);
	gl.patchParameteri(GL_PATCH_VERTICES, 6);

	{
		// Compute the number vertices in the largest draw call, so we can allocate the TF buffer just once.
		int maxNumVerticesInDrawCall = 0;
		for (int i = 0; i < numTessLevelCases; i++)
			maxNumVerticesInDrawCall = de::max(maxNumVerticesInDrawCall, referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &tessLevels[6*i+0], &tessLevels[6*i+2]));

		{
			const TFHandler tfHandler(m_context.getRenderContext(), maxNumVerticesInDrawCall);

			for (int tessLevelCaseNdx = 0; tessLevelCaseNdx < numTessLevelCases; tessLevelCaseNdx++)
			{
				log << TestLog::Message << "Testing with tessellation levels: " << tessellationLevelsString(&tessLevels[6*tessLevelCaseNdx+0], &tessLevels[6*tessLevelCaseNdx+2]) << TestLog::EndMessage;

				const glu::VertexArrayBinding bindings[] = { glu::va::Float("in_v_attr", 1, (int)6, 0, &tessLevels[6*tessLevelCaseNdx]) };
				const TFHandler::Result tfResult = tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
																					DE_LENGTH_OF_ARRAY(bindings), &bindings[0], 6);

				for (int vtxNdx = 0; vtxNdx < (int)tfResult.varying.size(); vtxNdx++)
				{
					const Vec3&		vec			= tfResult.varying[vtxNdx];
					const int		numComps	= m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? 3 : 2;

					for (int compNdx = 0; compNdx < numComps; compNdx++)
					{
						if (!checkTessCoordComponent(vec[compNdx]))
						{
							log << TestLog::Message << "Note: output value at index " << vtxNdx << " is "
													<< (m_primitiveType == TESSPRIMITIVETYPE_TRIANGLES ? de::toString(vec) : de::toString(vec.swizzle(0,1)))
													<< TestLog::EndMessage;
							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid tessellation coordinate component");
							return STOP;
						}
					}
				}
			}
		}
	}

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Test first part of invariance rule #8
 *
 * Test that all (relevant) components of tess coord are in [0,1].
 *//*--------------------------------------------------------------------*/
class TessCoordComponentRangeCase : public TessCoordComponentInvarianceCase
{
public:
	TessCoordComponentRangeCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
		: TessCoordComponentInvarianceCase(context, name, description, primType, spacing, winding, usePointMode)
	{
	}

protected:
	virtual string tessEvalOutputComponentStatements (const char* tessCoordComponentName, const char* outputComponentName) const
	{
		return string() + "\t" + outputComponentName + " = " + tessCoordComponentName + ";\n";
	}

	virtual bool checkTessCoordComponent (float component) const
	{
		if (!de::inRange(component, 0.0f, 1.0f))
		{
			m_testCtx.getLog() << TestLog::Message << "Failure: tess coord component isn't in range [0,1]" << TestLog::EndMessage;
			return false;
		}
		return true;
	}
};

/*--------------------------------------------------------------------*//*!
 * \brief Test second part of invariance rule #8
 *
 * Test that all (relevant) components of tess coord are in [0,1] and
 * 1.0-c is exact for every such component c.
 *//*--------------------------------------------------------------------*/
class OneMinusTessCoordComponentCase : public TessCoordComponentInvarianceCase
{
public:
	OneMinusTessCoordComponentCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
		: TessCoordComponentInvarianceCase(context, name, description, primType, spacing, winding, usePointMode)
	{
	}

protected:
	virtual string tessEvalOutputComponentStatements (const char* tessCoordComponentName, const char* outputComponentName) const
	{
		return string() + "	{\n"
						  "		float oneMinusComp = 1.0 - " + tessCoordComponentName + ";\n"
						  "		" + outputComponentName + " = " + tessCoordComponentName + " + oneMinusComp;\n"
						  "	}\n";
	}

	virtual bool checkTessCoordComponent (float component) const
	{
		if (component != 1.0f)
		{
			m_testCtx.getLog() << TestLog::Message << "Failure: comp + (1.0-comp) doesn't equal 1.0 for some component of tessellation coordinate" << TestLog::EndMessage;
			return false;
		}
		return true;
	}
};

/*--------------------------------------------------------------------*//*!
 * \brief Test that patch is discarded if relevant outer level <= 0.0
 *
 * Draws patches with different combinations of tessellation levels,
 * varying which levels are negative. Verifies by checking that colored
 * pixels exist inside the area of valid primitives, and only black pixels
 * exist inside the area of discarded primitives. An additional sanity
 * test is done, checking that the number of primitives written by TF is
 * correct.
 *//*--------------------------------------------------------------------*/
class PrimitiveDiscardCase : public TestCase
{
public:
	PrimitiveDiscardCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, SpacingMode spacing, Winding winding, bool usePointMode)
		: TestCase			(context, name, description)
		, m_primitiveType	(primType)
		, m_spacing			(spacing)
		, m_winding			(winding)
		, m_usePointMode	(usePointMode)
	{
	}

	void									init		(void);
	void									deinit		(void);
	IterateResult							iterate		(void);

private:
	static vector<float>					genAttributes (void);

	static const int						RENDER_SIZE = 256;

	const TessPrimitiveType					m_primitiveType;
	const SpacingMode						m_spacing;
	const Winding							m_winding;
	const bool								m_usePointMode;

	SharedPtr<const glu::ShaderProgram>		m_program;
};

void PrimitiveDiscardCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
		<< glu::VertexSource					("#version 310 es\n"
												 "\n"
												 "in highp float in_v_attr;\n"
												 "out highp float in_tc_attr;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	in_tc_attr = in_v_attr;\n"
												 "}\n")

		<< glu::TessellationControlSource		("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 "layout (vertices = 1) out;\n"
												 "\n"
												 "in highp float in_tc_attr[];\n"
												 "\n"
												 "patch out highp vec2 in_te_positionScale;\n"
												 "patch out highp vec2 in_te_positionOffset;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	in_te_positionScale  = vec2(in_tc_attr[6], in_tc_attr[7]);\n"
												 "	in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n"
												 "\n"
												 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
												 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
												 "\n"
												 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
												 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
												 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
												 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
												 "}\n")

		<< glu::TessellationEvaluationSource	("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 + getTessellationEvaluationInLayoutString(m_primitiveType, m_spacing, m_winding, m_usePointMode) +
												 "\n"
												 "patch in highp vec2 in_te_positionScale;\n"
												 "patch in highp vec2 in_te_positionOffset;\n"
												 "\n"
												 "out highp vec3 out_te_tessCoord;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	out_te_tessCoord = gl_TessCoord;\n"
												 "	gl_Position = vec4(gl_TessCoord.xy*in_te_positionScale + in_te_positionOffset, 0.0, 1.0);\n"
												 "}\n")

		<< glu::FragmentSource					("#version 310 es\n"
												 "\n"
												 "layout (location = 0) out mediump vec4 o_color;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	o_color = vec4(1.0);\n"
												 "}\n")

		<< glu::TransformFeedbackVarying		("out_te_tessCoord")
		<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void PrimitiveDiscardCase::deinit (void)
{
	m_program.clear();
}

vector<float> PrimitiveDiscardCase::genAttributes (void)
{
	// Generate input attributes (tessellation levels, and position scale and
	// offset) for a number of primitives. Each primitive has a different
	// combination of tessellatio levels; each level is either a valid
	// value or an "invalid" value (negative or zero, chosen from
	// invalidTessLevelChoices).

	// \note The attributes are generated in such an order that all of the
	//		 valid attribute tuples come before the first invalid one both
	//		 in the result vector, and when scanning the resulting 2d grid
	//		 of primitives is scanned in y-major order. This makes
	//		 verification somewhat simpler.

	static const float	baseTessLevels[6]			= { 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f };
	static const float	invalidTessLevelChoices[]	= { -0.42f, 0.0f };
	const int			numChoices					= 1 + DE_LENGTH_OF_ARRAY(invalidTessLevelChoices);
	float				choices[6][numChoices];
	vector<float>		result;

	for (int levelNdx = 0; levelNdx < 6; levelNdx++)
		for (int choiceNdx = 0; choiceNdx < numChoices; choiceNdx++)
			choices[levelNdx][choiceNdx] = choiceNdx == 0 ? baseTessLevels[levelNdx] : invalidTessLevelChoices[choiceNdx-1];

	{
		const int	numCols			= intPow(numChoices, 6/2); // sqrt(numChoices**6) == sqrt(number of primitives)
		const int	numRows			= numCols;
		int			index			= 0;
		int			i[6];
		// We could do this with some generic combination-generation function, but meh, it's not that bad.
		for (i[2] = 0; i[2] < numChoices; i[2]++) // First  outer
		for (i[3] = 0; i[3] < numChoices; i[3]++) // Second outer
		for (i[4] = 0; i[4] < numChoices; i[4]++) // Third  outer
		for (i[5] = 0; i[5] < numChoices; i[5]++) // Fourth outer
		for (i[0] = 0; i[0] < numChoices; i[0]++) // First  inner
		for (i[1] = 0; i[1] < numChoices; i[1]++) // Second inner
		{
			for (int j = 0; j < 6; j++)
				result.push_back(choices[j][i[j]]);

			{
				const int col = index % numCols;
				const int row = index / numCols;
				// Position scale.
				result.push_back((float)2.0f / (float)numCols);
				result.push_back((float)2.0f / (float)numRows);
				// Position offset.
				result.push_back((float)col / (float)numCols * 2.0f - 1.0f);
				result.push_back((float)row / (float)numRows * 2.0f - 1.0f);
			}

			index++;
		}
	}

	return result;
}

PrimitiveDiscardCase::IterateResult PrimitiveDiscardCase::iterate (void)
{
	typedef TransformFeedbackHandler<Vec3> TFHandler;

	TestLog&				log						= m_testCtx.getLog();
	const RenderContext&	renderCtx				= m_context.getRenderContext();
	const RandomViewport	viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const glw::Functions&	gl						= renderCtx.getFunctions();
	const vector<float>		attributes				= genAttributes();
	const int				numAttribsPerPrimitive	= 6+2+2; // Tess levels, scale, offset.
	const int				numPrimitives			= (int)attributes.size() / numAttribsPerPrimitive;
	const deUint32			programGL				= m_program->getProgram();

	gl.useProgram(programGL);
	setViewport(gl, viewport);
	gl.patchParameteri(GL_PATCH_VERTICES, numAttribsPerPrimitive);

	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);

	// Check the convenience assertion that all discarded patches come after the last non-discarded patch.
	{
		bool discardedPatchEncountered = false;
		for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++)
		{
			const bool discard = isPatchDiscarded(m_primitiveType, &attributes[numAttribsPerPrimitive*patchNdx + 2]);
			DE_ASSERT(discard || !discardedPatchEncountered);
			discardedPatchEncountered = discard;
		}
		DE_UNREF(discardedPatchEncountered);
	}

	{
		int numVerticesInDrawCall = 0;
		for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++)
			numVerticesInDrawCall += referenceVertexCount(m_primitiveType, m_spacing, m_usePointMode, &attributes[numAttribsPerPrimitive*patchNdx+0], &attributes[numAttribsPerPrimitive*patchNdx+2]);

		log << TestLog::Message << "Note: rendering " << numPrimitives << " patches; first patches have valid relevant outer levels, "
								<< "but later patches have one or more invalid (i.e. less than or equal to 0.0) relevant outer levels" << TestLog::EndMessage;

		{
			const TFHandler					tfHandler	(m_context.getRenderContext(), numVerticesInDrawCall);
			const glu::VertexArrayBinding	bindings[]	= { glu::va::Float("in_v_attr", 1, (int)attributes.size(), 0, &attributes[0]) };
			const TFHandler::Result			tfResult	= tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, m_usePointMode),
																						   DE_LENGTH_OF_ARRAY(bindings), &bindings[0], (int)attributes.size());
			const tcu::Surface				pixels		= getPixels(renderCtx, viewport);

			log << TestLog::Image("RenderedImage", "Rendered image", pixels);

			if ((int)tfResult.varying.size() != numVerticesInDrawCall)
			{
				log << TestLog::Message << "Failure: expected " << numVerticesInDrawCall << " vertices from transform feedback, got " << tfResult.varying.size() << TestLog::EndMessage;
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Wrong number of tessellation coordinates");
				return STOP;
			}

			// Check that white pixels are found around every non-discarded
			// patch, and that only black pixels are found after the last
			// non-discarded patch.
			{
				int lastWhitePixelRow									= 0;
				int secondToLastWhitePixelRow							= 0;
				int	lastWhitePixelColumnOnSecondToLastWhitePixelRow		= 0;

				for (int patchNdx = 0; patchNdx < numPrimitives; patchNdx++)
				{
					const float* const	attr			= &attributes[numAttribsPerPrimitive*patchNdx];
					const bool			validLevels		= !isPatchDiscarded(m_primitiveType, &attr[2]);

					if (validLevels)
					{
						// Not a discarded patch; check that at least one white pixel is found in its area.

						const float* const	scale		= &attr[6];
						const float* const	offset		= &attr[8];
						const int			x0			= (int)((			offset[0] + 1.0f)*0.5f*(float)pixels.getWidth()) - 1;
						const int			x1			= (int)((scale[0] + offset[0] + 1.0f)*0.5f*(float)pixels.getWidth()) + 1;
						const int			y0			= (int)((			offset[1] + 1.0f)*0.5f*(float)pixels.getHeight()) - 1;
						const int			y1			= (int)((scale[1] + offset[1] + 1.0f)*0.5f*(float)pixels.getHeight()) + 1;
						const bool			isMSAA		= renderCtx.getRenderTarget().getNumSamples() > 1;
						bool				pixelOk		= false;

						if (y1 > lastWhitePixelRow)
						{
							secondToLastWhitePixelRow	= lastWhitePixelRow;
							lastWhitePixelRow			= y1;
						}
						lastWhitePixelColumnOnSecondToLastWhitePixelRow = x1;

						for (int y = y0; y <= y1 && !pixelOk; y++)
						for (int x = x0; x <= x1 && !pixelOk; x++)
						{
							if (!de::inBounds(x, 0, pixels.getWidth()) || !de::inBounds(y, 0, pixels.getHeight()))
								continue;

							if (isMSAA)
							{
								if (pixels.getPixel(x, y) != tcu::RGBA::black())
									pixelOk = true;
							}
							else
							{
								if (pixels.getPixel(x, y) == tcu::RGBA::white())
									pixelOk = true;
							}
						}

						if (!pixelOk)
						{
							log << TestLog::Message << "Failure: expected at least one " << (isMSAA ? "non-black" : "white") << " pixel in the rectangle "
													<< "[x0=" << x0 << ", y0=" << y0 << ", x1=" << x1 << ", y1=" << y1 << "]" << TestLog::EndMessage
								<< TestLog::Message << "Note: the rectangle approximately corresponds to the patch with these tessellation levels: "
													<< tessellationLevelsString(&attr[0], &attr[1]) << TestLog::EndMessage;
							m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
							return STOP;
						}
					}
					else
					{
						// First discarded primitive patch; the remaining are guaranteed to be discarded ones as well.

						for (int y = 0; y < pixels.getHeight(); y++)
						for (int x = 0; x < pixels.getWidth(); x++)
						{
							if (y > lastWhitePixelRow || (y > secondToLastWhitePixelRow && x > lastWhitePixelColumnOnSecondToLastWhitePixelRow))
							{
								if (pixels.getPixel(x, y) != tcu::RGBA::black())
								{
									log << TestLog::Message << "Failure: expected all pixels to be black in the area "
															<< (lastWhitePixelColumnOnSecondToLastWhitePixelRow < pixels.getWidth()-1
																	? string() + "y > " + de::toString(lastWhitePixelRow) + " || (y > " + de::toString(secondToLastWhitePixelRow)
																			   + " && x > " + de::toString(lastWhitePixelColumnOnSecondToLastWhitePixelRow) + ")"
																	: string() + "y > " + de::toString(lastWhitePixelRow))
															<< " (they all correspond to patches that should be discarded)" << TestLog::EndMessage
										<< TestLog::Message << "Note: pixel " << tcu::IVec2(x, y) << " isn't black" << TestLog::EndMessage;
									m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
									return STOP;
								}
							}
						}

						break;
					}
				}
			}
		}
	}

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Case testing user-defined IO between TCS and TES
 *
 * TCS outputs various values to TES, including aggregates. The outputs
 * can be per-patch or per-vertex, and if per-vertex, they can also be in
 * an IO block. Per-vertex input array size can be left implicit (i.e.
 * inputArray[]) or explicit either by gl_MaxPatchVertices or an integer
 * literal whose value is queried from GL.
 *
 * The values output are generated in TCS and verified in TES against
 * similarly generated values. In case a verification of a value fails, the
 * index of the invalid value is output with TF.
 * As a sanity check, also the rendering result is verified (against pre-
 * rendered reference).
 *//*--------------------------------------------------------------------*/
class UserDefinedIOCase : public TestCase
{
public:
	enum IOType
	{
		IO_TYPE_PER_PATCH = 0,
		IO_TYPE_PER_PATCH_ARRAY,
		IO_TYPE_PER_PATCH_BLOCK,
		IO_TYPE_PER_PATCH_BLOCK_ARRAY,
		IO_TYPE_PER_VERTEX,
		IO_TYPE_PER_VERTEX_BLOCK,

		IO_TYPE_LAST
	};

	enum VertexIOArraySize
	{
		VERTEX_IO_ARRAY_SIZE_IMPLICIT = 0,
		VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN,		//!< Use gl_MaxPatchVertices as size for per-vertex input array.
		VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY,				//!< Query GL_MAX_PATCH_VERTICES, and use that as size for per-vertex input array.

		VERTEX_IO_ARRAY_SIZE_LAST
	};

	enum TessControlOutArraySize
	{
		TESS_CONTROL_OUT_ARRAY_SIZE_IMPLICIT = 0,
		TESS_CONTROL_OUT_ARRAY_SIZE_LAYOUT,
		TESS_CONTROL_OUT_ARRAY_SIZE_QUERY,
		TESS_CONTROL_OUT_ARRAY_SIZE_SHADER_BUILTIN
	};

	UserDefinedIOCase (Context& context, const char* name, const char* description, TessPrimitiveType primType, IOType ioType, VertexIOArraySize vertexIOArraySize, TessControlOutArraySize tessControlOutArraySize, const char* referenceImagePath)
		: TestCase					(context, name, description)
		, m_primitiveType			(primType)
		, m_ioType					(ioType)
		, m_vertexIOArraySize		(vertexIOArraySize)
		, m_tessControlOutArraySize	(tessControlOutArraySize)
		, m_referenceImagePath		(referenceImagePath)
	{
	}

	void									init		(void);
	void									deinit		(void);
	IterateResult							iterate		(void);

private:
	typedef string (*BasicTypeVisitFunc)(const string& name, glu::DataType type, int indentationDepth); //!< See glslTraverseBasicTypes below.

	class TopLevelObject
	{
	public:
		virtual			~TopLevelObject					(void) {}

		virtual string	name							(void) const = 0;
		virtual string	declare							(void) const = 0;
		virtual string	declareArray					(const string& arraySizeExpr) const = 0;
		virtual string	glslTraverseBasicTypeArray		(int numArrayElements, //!< If negative, traverse just array[gl_InvocationID], not all indices.
														 int indentationDepth,
														 BasicTypeVisitFunc) const = 0;
		virtual string	glslTraverseBasicType			(int indentationDepth,
														 BasicTypeVisitFunc) const = 0;
		virtual int		numBasicSubobjectsInElementType	(void) const = 0;
		virtual string	basicSubobjectAtIndex			(int index, int arraySize) const = 0;
	};

	class Variable : public TopLevelObject
	{
	public:
		Variable (const string& name_, const glu::VarType& type, bool isArray)
			: m_name		(name_)
			, m_type		(type)
			, m_isArray		(isArray)
		{
			DE_ASSERT(!type.isArrayType());
		}

		string	name								(void) const { return m_name; }
		string	declare								(void) const;
		string	declareArray						(const string& arraySizeExpr) const;
		string	glslTraverseBasicTypeArray			(int numArrayElements, int indentationDepth, BasicTypeVisitFunc) const;
		string	glslTraverseBasicType				(int indentationDepth, BasicTypeVisitFunc) const;
		int		numBasicSubobjectsInElementType		(void) const;
		string	basicSubobjectAtIndex				(int index, int arraySize) const;

	private:
		string			m_name;
		glu::VarType	m_type; //!< If this Variable is an array element, m_type is the element type; otherwise just the variable type.
		const bool		m_isArray;
	};

	class IOBlock : public TopLevelObject
	{
	public:
		struct Member
		{
			string			name;
			glu::VarType	type;
			Member (const string& n, const glu::VarType& t) : name(n), type(t) {}
		};

		IOBlock (const string& blockName, const string& interfaceName, const vector<Member>& members)
			: m_blockName		(blockName)
			, m_interfaceName	(interfaceName)
			, m_members			(members)
		{
		}

		string	name								(void) const { return m_interfaceName; }
		string	declare								(void) const;
		string	declareArray						(const string& arraySizeExpr) const;
		string	glslTraverseBasicTypeArray			(int numArrayElements, int indentationDepth, BasicTypeVisitFunc) const;
		string	glslTraverseBasicType				(int indentationDepth, BasicTypeVisitFunc) const;
		int		numBasicSubobjectsInElementType		(void) const;
		string	basicSubobjectAtIndex				(int index, int arraySize) const;

	private:
		string			m_blockName;
		string			m_interfaceName;
		vector<Member>	m_members;
	};

	static string							glslTraverseBasicTypes				(const string&			rootName,
																				 const glu::VarType&	rootType,
																				 int					arrayNestingDepth,
																				 int					indentationDepth,
																				 BasicTypeVisitFunc		visit);

	static string							glslAssignBasicTypeObject			(const string& name, glu::DataType, int indentationDepth);
	static string							glslCheckBasicTypeObject			(const string& name, glu::DataType, int indentationDepth);
	static int								numBasicSubobjectsInElementType		(const vector<SharedPtr<TopLevelObject> >&);
	static string							basicSubobjectAtIndex				(int index, const vector<SharedPtr<TopLevelObject> >&, int topLevelArraySizes);

	enum
	{
		RENDER_SIZE = 256
	};
	enum
	{
		NUM_OUTPUT_VERTICES = 5
	};
	enum
	{
		NUM_PER_PATCH_ARRAY_ELEMS = 3
	};
	enum
	{
		NUM_PER_PATCH_BLOCKS = 2
	};

	const TessPrimitiveType					m_primitiveType;
	const IOType							m_ioType;
	const VertexIOArraySize					m_vertexIOArraySize;
	const TessControlOutArraySize			m_tessControlOutArraySize;
	const string							m_referenceImagePath;

	vector<glu::StructType>					m_structTypes;
	vector<SharedPtr<TopLevelObject> >		m_tcsOutputs;
	vector<SharedPtr<TopLevelObject> >		m_tesInputs;

	SharedPtr<const glu::ShaderProgram>		m_program;
};

/*--------------------------------------------------------------------*//*!
 * \brief Generate GLSL code to traverse (possibly aggregate) object
 *
 * Generates a string that represents GLSL code that traverses the
 * basic-type subobjects in a rootType-typed object named rootName. Arrays
 * are traversed with loops and struct members are each traversed
 * separately. The code for each basic-type subobject is generated with
 * the function given as the 'visit' argument.
 *//*--------------------------------------------------------------------*/
string UserDefinedIOCase::glslTraverseBasicTypes (const string&			rootName,
												  const glu::VarType&	rootType,
												  int					arrayNestingDepth,
												  int					indentationDepth,
												  BasicTypeVisitFunc	visit)
{
	if (rootType.isBasicType())
		return visit(rootName, rootType.getBasicType(), indentationDepth);
	else if (rootType.isArrayType())
	{
		const string indentation	= string(indentationDepth, '\t');
		const string loopIndexName	= "i" + de::toString(arrayNestingDepth);
		const string arrayLength	= de::toString(rootType.getArraySize());
		return indentation + "for (int " + loopIndexName + " = 0; " + loopIndexName + " < " + de::toString(rootType.getArraySize()) + "; " + loopIndexName + "++)\n" +
			   indentation + "{\n" +
			   glslTraverseBasicTypes(rootName + "[" + loopIndexName + "]", rootType.getElementType(), arrayNestingDepth+1, indentationDepth+1, visit) +
			   indentation + "}\n";
	}
	else if (rootType.isStructType())
	{
		const glu::StructType&	structType = *rootType.getStructPtr();
		const int				numMembers = structType.getNumMembers();
		string					result;

		for (int membNdx = 0; membNdx < numMembers; membNdx++)
		{
			const glu::StructMember& member = structType.getMember(membNdx);
			result += glslTraverseBasicTypes(rootName + "." + member.getName(), member.getType(), arrayNestingDepth, indentationDepth, visit);
		}

		return result;
	}
	else
	{
		DE_ASSERT(false);
		return DE_NULL;
	}
}

string UserDefinedIOCase::Variable::declare (void) const
{
	DE_ASSERT(!m_isArray);
	return de::toString(glu::declare(m_type, m_name)) + ";\n";
}

string UserDefinedIOCase::Variable::declareArray (const string& sizeExpr) const
{
	DE_ASSERT(m_isArray);
	return de::toString(glu::declare(m_type, m_name)) + "[" + sizeExpr + "];\n";
}

string UserDefinedIOCase::IOBlock::declare (void) const
{
	std::ostringstream buf;

	buf << m_blockName << "\n"
		<< "{\n";

	for (int i = 0; i < (int)m_members.size(); i++)
		buf << "\t" << glu::declare(m_members[i].type, m_members[i].name) << ";\n";

	buf << "} " << m_interfaceName << ";\n";
	return buf.str();
}

string UserDefinedIOCase::IOBlock::declareArray (const string& sizeExpr) const
{
	std::ostringstream buf;

	buf << m_blockName << "\n"
		<< "{\n";

	for (int i = 0; i < (int)m_members.size(); i++)
		buf << "\t" << glu::declare(m_members[i].type, m_members[i].name) << ";\n";

	buf << "} " << m_interfaceName << "[" << sizeExpr << "];\n";
	return buf.str();
}

string UserDefinedIOCase::Variable::glslTraverseBasicTypeArray (int numArrayElements, int indentationDepth, BasicTypeVisitFunc visit) const
{
	DE_ASSERT(m_isArray);

	const bool				traverseAsArray		= numArrayElements >= 0;
	const string			traversedName		= m_name + (!traverseAsArray ? "[gl_InvocationID]" : "");
	const glu::VarType		type				= traverseAsArray ? glu::VarType(m_type, numArrayElements) : m_type;

	return UserDefinedIOCase::glslTraverseBasicTypes(traversedName, type, 0, indentationDepth, visit);
}

string UserDefinedIOCase::Variable::glslTraverseBasicType (int indentationDepth, BasicTypeVisitFunc visit) const
{
	DE_ASSERT(!m_isArray);

	return UserDefinedIOCase::glslTraverseBasicTypes(m_name, m_type, 0, indentationDepth, visit);
}

string UserDefinedIOCase::IOBlock::glslTraverseBasicTypeArray (int numArrayElements, int indentationDepth, BasicTypeVisitFunc visit) const
{
	if (numArrayElements >= 0)
	{
		const string	indentation			= string(indentationDepth, '\t');
		string			result				= indentation + "for (int i0 = 0; i0 < " + de::toString(numArrayElements) + "; i0++)\n" +
											  indentation + "{\n";
		for (int i = 0; i < (int)m_members.size(); i++)
			result += UserDefinedIOCase::glslTraverseBasicTypes(m_interfaceName + "[i0]." + m_members[i].name, m_members[i].type, 1, indentationDepth+1, visit);
		result += indentation + "}\n";
		return result;
	}
	else
	{
		string result;
		for (int i = 0; i < (int)m_members.size(); i++)
			result += UserDefinedIOCase::glslTraverseBasicTypes(m_interfaceName + "[gl_InvocationID]." + m_members[i].name, m_members[i].type, 0, indentationDepth, visit);
		return result;
	}
}


string UserDefinedIOCase::IOBlock::glslTraverseBasicType (int indentationDepth, BasicTypeVisitFunc visit) const
{
	string result;
	for (int i = 0; i < (int)m_members.size(); i++)
		result += UserDefinedIOCase::glslTraverseBasicTypes(m_interfaceName + "." + m_members[i].name, m_members[i].type, 0, indentationDepth, visit);
	return result;
}

int UserDefinedIOCase::Variable::numBasicSubobjectsInElementType (void) const
{
	return numBasicSubobjects(m_type);
}

int UserDefinedIOCase::IOBlock::numBasicSubobjectsInElementType (void) const
{
	int result = 0;
	for (int i = 0; i < (int)m_members.size(); i++)
		result += numBasicSubobjects(m_members[i].type);
	return result;
}

string UserDefinedIOCase::Variable::basicSubobjectAtIndex (int subobjectIndex, int arraySize) const
{
	const glu::VarType	type			= m_isArray ? glu::VarType(m_type, arraySize) : m_type;
	int					currentIndex	= 0;

	for (glu::BasicTypeIterator basicIt = glu::BasicTypeIterator::begin(&type);
		 basicIt != glu::BasicTypeIterator::end(&type);
		 ++basicIt)
	{
		if (currentIndex == subobjectIndex)
			return m_name + de::toString(glu::TypeAccessFormat(type, basicIt.getPath()));
		currentIndex++;
	}
	DE_ASSERT(false);
	return DE_NULL;
}

string UserDefinedIOCase::IOBlock::basicSubobjectAtIndex (int subobjectIndex, int arraySize) const
{
	int currentIndex = 0;
	for (int arrayNdx = 0; arrayNdx < arraySize; arrayNdx++)
	{
		for (int memberNdx = 0; memberNdx < (int)m_members.size(); memberNdx++)
		{
			const glu::VarType& membType = m_members[memberNdx].type;
			for (glu::BasicTypeIterator basicIt = glu::BasicTypeIterator::begin(&membType);
				 basicIt != glu::BasicTypeIterator::end(&membType);
				 ++basicIt)
			{
				if (currentIndex == subobjectIndex)
					return m_interfaceName + "[" + de::toString(arrayNdx) + "]." + m_members[memberNdx].name + de::toString(glu::TypeAccessFormat(membType, basicIt.getPath()));
				currentIndex++;
			}
		}
	}
	DE_ASSERT(false);
	return DE_NULL;
}

// Used as the 'visit' argument for glslTraverseBasicTypes.
string UserDefinedIOCase::glslAssignBasicTypeObject (const string& name, glu::DataType type, int indentationDepth)
{
	const int		scalarSize		= glu::getDataTypeScalarSize(type);
	const string	indentation		= string(indentationDepth, '\t');
	string			result;

	result += indentation + name + " = ";

	if (type != glu::TYPE_FLOAT)
		result += string() + glu::getDataTypeName(type) + "(";
	for (int i = 0; i < scalarSize; i++)
		result += (i > 0 ? ", v+" + de::floatToString(0.8f*(float)i, 1)
						 : "v");
	if (type != glu::TYPE_FLOAT)
		result += ")";
	result += ";\n" +
			  indentation + "v += 0.4;\n";
	return result;
}

// Used as the 'visit' argument for glslTraverseBasicTypes.
string UserDefinedIOCase::glslCheckBasicTypeObject (const string& name, glu::DataType type, int indentationDepth)
{
	const int		scalarSize		= glu::getDataTypeScalarSize(type);
	const string	indentation		= string(indentationDepth, '\t');
	string			result;

	result += indentation + "allOk = allOk && compare_" + glu::getDataTypeName(type) + "(" + name + ", ";

	if (type != glu::TYPE_FLOAT)
		result += string() + glu::getDataTypeName(type) + "(";
	for (int i = 0; i < scalarSize; i++)
		result += (i > 0 ? ", v+" + de::floatToString(0.8f*(float)i, 1)
						 : "v");
	if (type != glu::TYPE_FLOAT)
		result += ")";
	result += ");\n" +
			  indentation + "v += 0.4;\n" +
			  indentation + "if (allOk) firstFailedInputIndex++;\n";

	return result;
}

int UserDefinedIOCase::numBasicSubobjectsInElementType (const vector<SharedPtr<TopLevelObject> >& objects)
{
	int result = 0;
	for (int i = 0; i < (int)objects.size(); i++)
		result += objects[i]->numBasicSubobjectsInElementType();
	return result;
}

string UserDefinedIOCase::basicSubobjectAtIndex (int subobjectIndex, const vector<SharedPtr<TopLevelObject> >& objects, int topLevelArraySize)
{
	int currentIndex	= 0;
	int objectIndex		= 0;
	for (; currentIndex < subobjectIndex; objectIndex++)
		currentIndex += objects[objectIndex]->numBasicSubobjectsInElementType() * topLevelArraySize;
	if (currentIndex > subobjectIndex)
	{
		objectIndex--;
		currentIndex -= objects[objectIndex]->numBasicSubobjectsInElementType() * topLevelArraySize;
	}

	return objects[objectIndex]->basicSubobjectAtIndex(subobjectIndex - currentIndex, topLevelArraySize);
}

void UserDefinedIOCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	const bool			isPerPatchIO				= m_ioType == IO_TYPE_PER_PATCH				||
													  m_ioType == IO_TYPE_PER_PATCH_ARRAY		||
													  m_ioType == IO_TYPE_PER_PATCH_BLOCK		||
													  m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY;

	const bool			isExplicitVertexArraySize	= m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN ||
													  m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY;

	const string		vertexAttrArrayInputSize	= m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_IMPLICIT					? ""
													: m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN	? "gl_MaxPatchVertices"
													: m_vertexIOArraySize == VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY			? de::toString(m_context.getContextInfo().getInt(GL_MAX_PATCH_VERTICES))
													: DE_NULL;

	const char* const	maybePatch					= isPerPatchIO ? "patch " : "";
	const string		outMaybePatch				= string() + maybePatch + "out ";
	const string		inMaybePatch				= string() + maybePatch + "in ";
	const bool			useBlock					= m_ioType == IO_TYPE_PER_VERTEX_BLOCK		||
													  m_ioType == IO_TYPE_PER_PATCH_BLOCK		||
													  m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY;

	string				tcsDeclarations;
	string				tcsStatements;

	string				tesDeclarations;
	string				tesStatements;

	{
		m_structTypes.push_back(glu::StructType("S"));

		const glu::VarType	highpFloat		(glu::TYPE_FLOAT, glu::PRECISION_HIGHP);
		glu::StructType&	structType		= m_structTypes.back();
		const glu::VarType	structVarType	(&structType);
		bool				usedStruct		= false;

		structType.addMember("x", glu::VarType(glu::TYPE_INT, glu::PRECISION_HIGHP));
		structType.addMember("y", glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP));

		if (useBlock)
		{
			// It is illegal to have a structure containing an array as an output variable
			structType.addMember("z", glu::VarType(highpFloat, 2));
		}

		if (useBlock)
		{
			const bool				useLightweightBlock = (m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY); // use leaner block to make sure it is not larger than allowed (per-patch storage is very limited)
			vector<IOBlock::Member>	blockMembers;

			if (!useLightweightBlock)
				blockMembers.push_back(IOBlock::Member("blockS",	structVarType));

			blockMembers.push_back(IOBlock::Member("blockFa",	glu::VarType(highpFloat, 3)));
			blockMembers.push_back(IOBlock::Member("blockSa",	glu::VarType(structVarType, 2)));
			blockMembers.push_back(IOBlock::Member("blockF",	highpFloat));

			m_tcsOutputs.push_back	(SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "tcBlock", blockMembers)));
			m_tesInputs.push_back	(SharedPtr<TopLevelObject>(new IOBlock("TheBlock", "teBlock", blockMembers)));

			usedStruct = true;
		}
		else
		{
			const Variable var0("in_te_s", structVarType,	m_ioType != IO_TYPE_PER_PATCH);
			const Variable var1("in_te_f", highpFloat,		m_ioType != IO_TYPE_PER_PATCH);

			if (m_ioType != IO_TYPE_PER_PATCH_ARRAY)
			{
				// Arrays of structures are disallowed, add struct cases only if not arrayed variable
				m_tcsOutputs.push_back	(SharedPtr<TopLevelObject>(new Variable(var0)));
				m_tesInputs.push_back	(SharedPtr<TopLevelObject>(new Variable(var0)));

				usedStruct = true;
			}

			m_tcsOutputs.push_back	(SharedPtr<TopLevelObject>(new Variable(var1)));
			m_tesInputs.push_back	(SharedPtr<TopLevelObject>(new Variable(var1)));
		}

		tcsDeclarations += "in " + Variable("in_tc_attr", highpFloat, true).declareArray(vertexAttrArrayInputSize);

		if (usedStruct)
			tcsDeclarations += de::toString(glu::declare(structType)) + ";\n";

		tcsStatements += "\t{\n"
						 "\t\thighp float v = 1.3;\n";

		for (int tcsOutputNdx = 0; tcsOutputNdx < (int)m_tcsOutputs.size(); tcsOutputNdx++)
		{
			const TopLevelObject&	output		= *m_tcsOutputs[tcsOutputNdx];
			const int				numElements	= !isPerPatchIO								? -1	//!< \note -1 means indexing with gl_InstanceID
												: m_ioType == IO_TYPE_PER_PATCH				? 1
												: m_ioType == IO_TYPE_PER_PATCH_ARRAY		? NUM_PER_PATCH_ARRAY_ELEMS
												: m_ioType == IO_TYPE_PER_PATCH_BLOCK		? 1
												: m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY	? NUM_PER_PATCH_BLOCKS
												: -2;
			const bool				isArray		= (numElements != 1);

			DE_ASSERT(numElements != -2);

			if (isArray)
			{
				tcsDeclarations += outMaybePatch + output.declareArray(m_ioType == IO_TYPE_PER_PATCH_ARRAY											? de::toString(int(NUM_PER_PATCH_ARRAY_ELEMS))
																	   : m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY									? de::toString(int(NUM_PER_PATCH_BLOCKS))
																	   : m_tessControlOutArraySize == TESS_CONTROL_OUT_ARRAY_SIZE_LAYOUT			? de::toString(int(NUM_OUTPUT_VERTICES))
																	   : m_tessControlOutArraySize == TESS_CONTROL_OUT_ARRAY_SIZE_QUERY				? de::toString(m_context.getContextInfo().getInt(GL_MAX_PATCH_VERTICES))
																	   : m_tessControlOutArraySize == TESS_CONTROL_OUT_ARRAY_SIZE_SHADER_BUILTIN	? "gl_MaxPatchVertices"
																	   : "");
			}
			else
				tcsDeclarations += outMaybePatch + output.declare();

			if (!isPerPatchIO)
				tcsStatements += "\t\tv += float(gl_InvocationID)*" + de::floatToString(0.4f * (float)output.numBasicSubobjectsInElementType(), 1) + ";\n";

			tcsStatements += "\n\t\t// Assign values to output " + output.name() + "\n";
			if (isArray)
				tcsStatements += output.glslTraverseBasicTypeArray(numElements, 2, glslAssignBasicTypeObject);
			else
				tcsStatements += output.glslTraverseBasicType(2, glslAssignBasicTypeObject);

			if (!isPerPatchIO)
				tcsStatements += "\t\tv += float(" + de::toString(int(NUM_OUTPUT_VERTICES)) + "-gl_InvocationID-1)*" + de::floatToString(0.4f * (float)output.numBasicSubobjectsInElementType(), 1) + ";\n";
		}
		tcsStatements += "\t}\n";

		if (usedStruct)
			tesDeclarations += de::toString(glu::declare(structType)) + ";\n";

		tesStatements += "\tbool allOk = true;\n"
						 "\thighp uint firstFailedInputIndex = 0u;\n"
						 "\t{\n"
						 "\t\thighp float v = 1.3;\n";
		for (int tesInputNdx = 0; tesInputNdx < (int)m_tesInputs.size(); tesInputNdx++)
		{
			const TopLevelObject&	input		= *m_tesInputs[tesInputNdx];
			const int				numElements	= !isPerPatchIO								? (int)NUM_OUTPUT_VERTICES
												: m_ioType == IO_TYPE_PER_PATCH				? 1
												: m_ioType == IO_TYPE_PER_PATCH_BLOCK		? 1
												: m_ioType == IO_TYPE_PER_PATCH_ARRAY		? NUM_PER_PATCH_ARRAY_ELEMS
												: m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY	? NUM_PER_PATCH_BLOCKS
												: -2;
			const bool				isArray		= (numElements != 1);

			DE_ASSERT(numElements != -2);

			if (isArray)
				tesDeclarations += inMaybePatch + input.declareArray(m_ioType == IO_TYPE_PER_PATCH_ARRAY			? de::toString(int(NUM_PER_PATCH_ARRAY_ELEMS))
																	 : m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY	? de::toString(int(NUM_PER_PATCH_BLOCKS))
																	 : isExplicitVertexArraySize					? de::toString(vertexAttrArrayInputSize)
																	 : "");
			else
				tesDeclarations += inMaybePatch + input.declare();

			tesStatements += "\n\t\t// Check values in input " + input.name() + "\n";
			if (isArray)
				tesStatements += input.glslTraverseBasicTypeArray(numElements, 2, glslCheckBasicTypeObject);
			else
				tesStatements += input.glslTraverseBasicType(2, glslCheckBasicTypeObject);
		}
		tesStatements += "\t}\n";
	}

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
		<< glu::VertexSource					("#version 310 es\n"
												 "\n"
												 "in highp float in_v_attr;\n"
												 "out highp float in_tc_attr;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	in_tc_attr = in_v_attr;\n"
												 "}\n")

		<< glu::TessellationControlSource		("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 "layout (vertices = " + de::toString(int(NUM_OUTPUT_VERTICES)) + ") out;\n"
												 "\n"
												 + tcsDeclarations +
												 "\n"
												 "patch out highp vec2 in_te_positionScale;\n"
												 "patch out highp vec2 in_te_positionOffset;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 + tcsStatements +
												 "\n"
												 "	in_te_positionScale  = vec2(in_tc_attr[6], in_tc_attr[7]);\n"
												 "	in_te_positionOffset = vec2(in_tc_attr[8], in_tc_attr[9]);\n"
												 "\n"
												 "	gl_TessLevelInner[0] = in_tc_attr[0];\n"
												 "	gl_TessLevelInner[1] = in_tc_attr[1];\n"
												 "\n"
												 "	gl_TessLevelOuter[0] = in_tc_attr[2];\n"
												 "	gl_TessLevelOuter[1] = in_tc_attr[3];\n"
												 "	gl_TessLevelOuter[2] = in_tc_attr[4];\n"
												 "	gl_TessLevelOuter[3] = in_tc_attr[5];\n"
												 "}\n")

		<< glu::TessellationEvaluationSource	("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 + getTessellationEvaluationInLayoutString(m_primitiveType) +
												 "\n"
												 + tesDeclarations +
												 "\n"
												 "patch in highp vec2 in_te_positionScale;\n"
												 "patch in highp vec2 in_te_positionOffset;\n"
												 "\n"
												 "out highp vec4 in_f_color;\n"
												 "// Will contain the index of the first incorrect input,\n"
												 "// or the number of inputs if all are correct\n"
												 "flat out highp uint out_te_firstFailedInputIndex;\n"
												 "\n"
												 "bool compare_int   (int   a, int   b) { return a == b; }\n"
												 "bool compare_float (float a, float b) { return abs(a - b) < 0.01f; }\n"
												 "bool compare_vec4  (vec4  a, vec4  b) { return all(lessThan(abs(a - b), vec4(0.01f))); }\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 + tesStatements +
												 "\n"
												 "	gl_Position = vec4(gl_TessCoord.xy*in_te_positionScale + in_te_positionOffset, 0.0, 1.0);\n"
												 "	in_f_color = allOk ? vec4(0.0, 1.0, 0.0, 1.0)\n"
												 "	                   : vec4(1.0, 0.0, 0.0, 1.0);\n"
												 "	out_te_firstFailedInputIndex = firstFailedInputIndex;\n"
												 "}\n")

		<< glu::FragmentSource					("#version 310 es\n"
												 "\n"
												 "layout (location = 0) out mediump vec4 o_color;\n"
												 "\n"
												 "in highp vec4 in_f_color;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	o_color = in_f_color;\n"
												 "}\n")

		<< glu::TransformFeedbackVarying		("out_te_firstFailedInputIndex")
		<< glu::TransformFeedbackMode			(GL_INTERLEAVED_ATTRIBS)));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void UserDefinedIOCase::deinit (void)
{
	m_program.clear();
}

UserDefinedIOCase::IterateResult UserDefinedIOCase::iterate (void)
{
	typedef TransformFeedbackHandler<deUint32> TFHandler;

	TestLog&				log						= m_testCtx.getLog();
	const RenderContext&	renderCtx				= m_context.getRenderContext();
	const RandomViewport	viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const glw::Functions&	gl						= renderCtx.getFunctions();
	static const float		attributes[6+2+2]		= { /* inner */ 3.0f, 4.0f, /* outer */ 5.0f, 6.0f, 7.0f, 8.0f, /* pos. scale */ 1.2f, 1.3f, /* pos. offset */ -0.3f, -0.4f };
	const deUint32			programGL				= m_program->getProgram();
	const int				numVertices				= referenceVertexCount(m_primitiveType, SPACINGMODE_EQUAL, false, &attributes[0], &attributes[2]);
	const TFHandler			tfHandler				(renderCtx, numVertices);
	tcu::ResultCollector	result;

	gl.useProgram(programGL);
	setViewport(gl, viewport);
	gl.patchParameteri(GL_PATCH_VERTICES, DE_LENGTH_OF_ARRAY(attributes));

	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);

	{
		const glu::VertexArrayBinding	bindings[]	= { glu::va::Float("in_v_attr", 1, DE_LENGTH_OF_ARRAY(attributes), 0, &attributes[0]) };
		const TFHandler::Result			tfResult	= tfHandler.renderAndGetPrimitives(programGL, outputPrimitiveTypeGL(m_primitiveType, false),
																					   DE_LENGTH_OF_ARRAY(bindings), &bindings[0], DE_LENGTH_OF_ARRAY(attributes));

		{
			const tcu::Surface			pixels		= getPixels(renderCtx, viewport);
			const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePath.c_str());
			const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), pixels.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);

			if (!success)
				result.fail("Image comparison failed");
		}

		if ((int)tfResult.varying.size() != numVertices)
		{
			log << TestLog::Message << "Failure: transform feedback returned " << tfResult.varying.size() << " vertices; expected " << numVertices << TestLog::EndMessage;
			result.fail("Wrong number of vertices");
		}
		else
		{
			const int topLevelArraySize		= (m_ioType == IO_TYPE_PER_PATCH				? 1
											 : m_ioType == IO_TYPE_PER_PATCH_ARRAY			? NUM_PER_PATCH_ARRAY_ELEMS
											 : m_ioType == IO_TYPE_PER_PATCH_BLOCK			? 1
											 : m_ioType == IO_TYPE_PER_PATCH_BLOCK_ARRAY	? NUM_PER_PATCH_BLOCKS
											 : (int)NUM_OUTPUT_VERTICES);
			const int numTEInputs			= numBasicSubobjectsInElementType(m_tesInputs) * topLevelArraySize;

			for (int vertexNdx = 0; vertexNdx < (int)numVertices; vertexNdx++)
			{
				if (tfResult.varying[vertexNdx] > (deUint32)numTEInputs)
				{
					log << TestLog::Message << "Failure: out_te_firstFailedInputIndex has value " << tfResult.varying[vertexNdx]
											<< ", should be in range [0, " << numTEInputs << "]" << TestLog::EndMessage;
					result.fail("Invalid transform feedback output");
				}
				else if (tfResult.varying[vertexNdx] != (deUint32)numTEInputs)
				{
					log << TestLog::Message << "Failure: in tessellation evaluation shader, check for input "
											<< basicSubobjectAtIndex(tfResult.varying[vertexNdx], m_tesInputs, topLevelArraySize) << " failed" << TestLog::EndMessage;
					result.fail("Invalid input value in tessellation evaluation shader");
				}
			}
		}
	}

	result.setTestContextResult(m_testCtx);
	return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Pass gl_Position between VS and TCS, or between TCS and TES.
 *
 * In TCS gl_Position is in the gl_out[] block and in TES in the gl_in[]
 * block, and has no special semantics in those. Arbitrary vec4 data can
 * thus be passed there.
 *//*--------------------------------------------------------------------*/
class GLPositionCase : public TestCase
{
public:
	enum CaseType
	{
		CASETYPE_VS_TO_TCS = 0,
		CASETYPE_TCS_TO_TES,
		CASETYPE_VS_TO_TCS_TO_TES,

		CASETYPE_LAST
	};

	GLPositionCase (Context& context, const char* name, const char* description, CaseType caseType, const char* referenceImagePath)
		: TestCase				(context, name, description)
		, m_caseType			(caseType)
		, m_referenceImagePath	(referenceImagePath)
	{
	}

	void									init				(void);
	void									deinit				(void);
	IterateResult							iterate				(void);

	static const char*						getCaseTypeName		(CaseType type);

private:
	static const int						RENDER_SIZE = 256;

	const CaseType							m_caseType;
	const string							m_referenceImagePath;

	SharedPtr<const glu::ShaderProgram>		m_program;
};

const char* GLPositionCase::getCaseTypeName (CaseType type)
{
	switch (type)
	{
		case CASETYPE_VS_TO_TCS:			return "gl_position_vs_to_tcs";
		case CASETYPE_TCS_TO_TES:			return "gl_position_tcs_to_tes";
		case CASETYPE_VS_TO_TCS_TO_TES:		return "gl_position_vs_to_tcs_to_tes";
		default:
			DE_ASSERT(false); return DE_NULL;
	}
}

void GLPositionCase::init (void)
{
	checkTessellationSupport(m_context);
	checkRenderTargetSize(m_context.getRenderTarget(), RENDER_SIZE);

	const bool		vsToTCS		= m_caseType == CASETYPE_VS_TO_TCS		|| m_caseType == CASETYPE_VS_TO_TCS_TO_TES;
	const bool		tcsToTES	= m_caseType == CASETYPE_TCS_TO_TES		|| m_caseType == CASETYPE_VS_TO_TCS_TO_TES;

	const string	tesIn0		= tcsToTES ? "gl_in[0].gl_Position" : "in_te_attr[0]";
	const string	tesIn1		= tcsToTES ? "gl_in[1].gl_Position" : "in_te_attr[1]";
	const string	tesIn2		= tcsToTES ? "gl_in[2].gl_Position" : "in_te_attr[2]";

	m_program = SharedPtr<const ShaderProgram>(new ShaderProgram(m_context.getRenderContext(), glu::ProgramSources()
		<< glu::VertexSource					("#version 310 es\n"
												 "\n"
												 "in highp vec4 in_v_attr;\n"
												 + string(!vsToTCS ? "out highp vec4 in_tc_attr;\n" : "") +
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	" + (vsToTCS ? "gl_Position" : "in_tc_attr") + " = in_v_attr;\n"
												 "}\n")

		<< glu::TessellationControlSource		("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 "layout (vertices = 3) out;\n"
												 "\n"
												 + string(!vsToTCS ? "in highp vec4 in_tc_attr[];\n" : "") +
												 "\n"
												 + (!tcsToTES ? "out highp vec4 in_te_attr[];\n" : "") +
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	" + (tcsToTES ? "gl_out[gl_InvocationID].gl_Position" : "in_te_attr[gl_InvocationID]") + " = "
													  + (vsToTCS ? "gl_in[gl_InvocationID].gl_Position" : "in_tc_attr[gl_InvocationID]") + ";\n"
												 "\n"
												 "	gl_TessLevelInner[0] = 2.0;\n"
												 "	gl_TessLevelInner[1] = 3.0;\n"
												 "\n"
												 "	gl_TessLevelOuter[0] = 4.0;\n"
												 "	gl_TessLevelOuter[1] = 5.0;\n"
												 "	gl_TessLevelOuter[2] = 6.0;\n"
												 "	gl_TessLevelOuter[3] = 7.0;\n"
												 "}\n")

		<< glu::TessellationEvaluationSource	("#version 310 es\n"
												 "#extension GL_EXT_tessellation_shader : require\n"
												 "\n"
												 + getTessellationEvaluationInLayoutString(TESSPRIMITIVETYPE_TRIANGLES) +
												 "\n"
												 + (!tcsToTES ? "in highp vec4 in_te_attr[];\n" : "") +
												 "\n"
												 "out highp vec4 in_f_color;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	highp vec2 xy = gl_TessCoord.x * " + tesIn0 + ".xy\n"
												 "	              + gl_TessCoord.y * " + tesIn1 + ".xy\n"
												 "	              + gl_TessCoord.z * " + tesIn2 + ".xy;\n"
												 "	gl_Position = vec4(xy, 0.0, 1.0);\n"
												 "	in_f_color = vec4(" + tesIn0 + ".z + " + tesIn1 + ".w,\n"
												 "	                  " + tesIn2 + ".z + " + tesIn0 + ".w,\n"
												 "	                  " + tesIn1 + ".z + " + tesIn2 + ".w,\n"
												 "	                  1.0);\n"
												 "}\n")

		<< glu::FragmentSource					("#version 310 es\n"
												 "\n"
												 "layout (location = 0) out mediump vec4 o_color;\n"
												 "\n"
												 "in highp vec4 in_f_color;\n"
												 "\n"
												 "void main (void)\n"
												 "{\n"
												 "	o_color = in_f_color;\n"
												 "}\n")));

	m_testCtx.getLog() << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Program compilation failed");
}

void GLPositionCase::deinit (void)
{
	m_program.clear();
}

GLPositionCase::IterateResult GLPositionCase::iterate (void)
{
	TestLog&				log						= m_testCtx.getLog();
	const RenderContext&	renderCtx				= m_context.getRenderContext();
	const RandomViewport	viewport				(renderCtx.getRenderTarget(), RENDER_SIZE, RENDER_SIZE, deStringHash(getName()));
	const glw::Functions&	gl						= renderCtx.getFunctions();
	const deUint32			programGL				= m_program->getProgram();

	static const float attributes[3*4] =
	{
		-0.8f, -0.7f, 0.1f, 0.7f,
		-0.5f,  0.4f, 0.2f, 0.5f,
		 0.3f,  0.2f, 0.3f, 0.45f
	};

	gl.useProgram(programGL);
	setViewport(gl, viewport);
	gl.patchParameteri(GL_PATCH_VERTICES, 3);

	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);

	log << TestLog::Message << "Note: input data for in_v_attr:\n" << arrayStr(attributes, 4) << TestLog::EndMessage;

	{
		const glu::VertexArrayBinding bindings[] = { glu::va::Float("in_v_attr", 4, 3, 0, &attributes[0]) };
		glu::draw(renderCtx, programGL, DE_LENGTH_OF_ARRAY(bindings), &bindings[0], glu::pr::Patches(3));

		{
			const tcu::Surface			pixels		= getPixels(renderCtx, viewport);
			const tcu::TextureLevel		reference	= getPNG(m_testCtx.getArchive(), m_referenceImagePath.c_str());
			const bool					success		= tcu::fuzzyCompare(log, "ImageComparison", "Image Comparison", reference.getAccess(), pixels.getAccess(), 0.02f, tcu::COMPARE_LOG_RESULT);

			if (!success)
			{
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
				return STOP;
			}
		}
	}

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	return STOP;
}

class LimitQueryCase : public TestCase
{
public:
						LimitQueryCase	(Context& context, const char* name, const char* desc, glw::GLenum target, int minValue);
private:
	IterateResult		iterate			(void);

	const glw::GLenum	m_target;
	const int			m_minValue;
};

LimitQueryCase::LimitQueryCase (Context& context, const char* name, const char* desc, glw::GLenum target, int minValue)
	: TestCase			(context, name, desc)
	, m_target			(target)
	, m_minValue		(minValue)
{
}

LimitQueryCase::IterateResult LimitQueryCase::iterate (void)
{
	checkTessellationSupport(m_context);

	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");

	gl.enableLogging(true);
	verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_INTEGER);

	{
		const tcu::ScopedLogSection	section(m_testCtx.getLog(), "Types", "Alternative queries");
		verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_BOOLEAN);
		verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_INTEGER64);
		verifyStateIntegerMin(result, gl, m_target, m_minValue, QUERY_FLOAT);
	}

	result.setTestContextResult(m_testCtx);
	return STOP;
}

class CombinedUniformLimitCase : public TestCase
{
public:
						CombinedUniformLimitCase	(Context& context, const char* name, const char* desc, glw::GLenum combined, glw::GLenum numBlocks, glw::GLenum defaultComponents);
private:
	IterateResult		iterate						(void);

	const glw::GLenum	m_combined;
	const glw::GLenum	m_numBlocks;
	const glw::GLenum	m_defaultComponents;
};

CombinedUniformLimitCase::CombinedUniformLimitCase (Context& context, const char* name, const char* desc, glw::GLenum combined, glw::GLenum numBlocks, glw::GLenum defaultComponents)
	: TestCase				(context, name, desc)
	, m_combined			(combined)
	, m_numBlocks			(numBlocks)
	, m_defaultComponents	(defaultComponents)
{
}

CombinedUniformLimitCase::IterateResult CombinedUniformLimitCase::iterate (void)
{
	checkTessellationSupport(m_context);

	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");

	gl.enableLogging(true);

	m_testCtx.getLog()	<< tcu::TestLog::Message
						<< "The minimum value of " << glu::getGettableStateStr(m_combined)
						<< " is " << glu::getGettableStateStr(m_numBlocks)
						<< " x MAX_UNIFORM_BLOCK_SIZE / 4 + "
						<< glu::getGettableStateStr(m_defaultComponents)
						<< tcu::TestLog::EndMessage;

	StateQueryMemoryWriteGuard<glw::GLint> maxUniformBlocks;
	gl.glGetIntegerv(m_numBlocks, &maxUniformBlocks);
	GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glGetIntegerv");

	StateQueryMemoryWriteGuard<glw::GLint> maxUniformBlockSize;
	gl.glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize);
	GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glGetIntegerv");

	StateQueryMemoryWriteGuard<glw::GLint> maxUniformComponents;
	gl.glGetIntegerv(m_defaultComponents, &maxUniformComponents);
	GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glGetIntegerv");

	if (maxUniformBlocks.verifyValidity(result) && maxUniformBlockSize.verifyValidity(result) && maxUniformComponents.verifyValidity(result))
	{
		const int limit = ((int)maxUniformBlocks) * ((int)maxUniformBlockSize) / 4 + (int)maxUniformComponents;
		verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_INTEGER);

		{
			const tcu::ScopedLogSection	section(m_testCtx.getLog(), "Types", "Alternative queries");
			verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_BOOLEAN);
			verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_INTEGER64);
			verifyStateIntegerMin(result, gl, m_combined, limit, QUERY_FLOAT);
		}
	}

	result.setTestContextResult(m_testCtx);
	return STOP;
}

class PatchVerticesStateCase : public TestCase
{
public:
						PatchVerticesStateCase	(Context& context, const char* name, const char* desc);
private:
	IterateResult		iterate					(void);
};

PatchVerticesStateCase::PatchVerticesStateCase (Context& context, const char* name, const char* desc)
	: TestCase(context, name, desc)
{
}

PatchVerticesStateCase::IterateResult PatchVerticesStateCase::iterate (void)
{
	checkTessellationSupport(m_context);

	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");

	gl.enableLogging(true);

	// initial
	{
		const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial value");

		verifyStateInteger(result, gl, GL_PATCH_VERTICES, 3, QUERY_INTEGER);
	}

	// bind
	{
		const tcu::ScopedLogSection section(m_testCtx.getLog(), "set", "After set");

		gl.glPatchParameteri(GL_PATCH_VERTICES, 22);
		GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "glPatchParameteri");

		verifyStateInteger(result, gl, GL_PATCH_VERTICES, 22, QUERY_INTEGER);
		{
			const tcu::ScopedLogSection	subsection(m_testCtx.getLog(), "Types", "Alternative queries");
			verifyStateIntegerMin(result, gl, GL_PATCH_VERTICES, 22, QUERY_BOOLEAN);
			verifyStateIntegerMin(result, gl, GL_PATCH_VERTICES, 22, QUERY_INTEGER64);
			verifyStateIntegerMin(result, gl, GL_PATCH_VERTICES, 22, QUERY_FLOAT);
		}
	}

	result.setTestContextResult(m_testCtx);
	return STOP;
}

class PrimitiveRestartForPatchesSupportedCase : public TestCase
{
public:
						PrimitiveRestartForPatchesSupportedCase	(Context& context, const char* name, const char* desc);
private:
	IterateResult		iterate									(void);
};

PrimitiveRestartForPatchesSupportedCase::PrimitiveRestartForPatchesSupportedCase (Context& context, const char* name, const char* desc)
	: TestCase(context, name, desc)
{
}

PrimitiveRestartForPatchesSupportedCase::IterateResult PrimitiveRestartForPatchesSupportedCase::iterate (void)
{
	checkTessellationSupport(m_context);

	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
	QueriedState			state;

	gl.enableLogging(true);

	queryState(result, gl, QUERY_BOOLEAN, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state);

	if (!state.isUndefined())
	{
		const tcu::ScopedLogSection	subsection(m_testCtx.getLog(), "Types", "Alternative types");
		verifyStateBoolean(result, gl, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state.getBoolAccess(), QUERY_INTEGER);
		verifyStateBoolean(result, gl, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state.getBoolAccess(), QUERY_INTEGER64);
		verifyStateBoolean(result, gl, GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, state.getBoolAccess(), QUERY_FLOAT);
	}

	result.setTestContextResult(m_testCtx);
	return STOP;
}

class TessProgramQueryCase : public TestCase
{
public:
						TessProgramQueryCase	(Context& context, const char* name, const char* desc);

	std::string			getVertexSource			(void) const;
	std::string			getFragmentSource		(void) const;
	std::string			getTessCtrlSource		(const char* globalLayouts) const;
	std::string			getTessEvalSource		(const char* globalLayouts) const;
};

TessProgramQueryCase::TessProgramQueryCase (Context& context, const char* name, const char* desc)
	: TestCase(context, name, desc)
{
}

std::string TessProgramQueryCase::getVertexSource (void) const
{
	return	"#version 310 es\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = vec4(float(gl_VertexID), float(gl_VertexID / 2), 0.0, 1.0);\n"
			"}\n";
}

std::string TessProgramQueryCase::getFragmentSource (void) const
{
	return	"#version 310 es\n"
			"layout (location = 0) out mediump vec4 o_color;\n"
			"void main (void)\n"
			"{\n"
			"	o_color = vec4(0.0, 1.0, 0.0, 1.0);\n"
			"}\n";
}

std::string TessProgramQueryCase::getTessCtrlSource (const char* globalLayouts) const
{
	return	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			+ std::string(globalLayouts) + ";\n"
			"void main (void)\n"
			"{\n"
			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
			"	gl_TessLevelInner[0] = 2.8;\n"
			"	gl_TessLevelInner[1] = 2.8;\n"
			"	gl_TessLevelOuter[0] = 2.8;\n"
			"	gl_TessLevelOuter[1] = 2.8;\n"
			"	gl_TessLevelOuter[2] = 2.8;\n"
			"	gl_TessLevelOuter[3] = 2.8;\n"
			"}\n";
}

std::string TessProgramQueryCase::getTessEvalSource (const char* globalLayouts) const
{
	return	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			+ std::string(globalLayouts) + ";\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = gl_TessCoord.x * gl_in[0].gl_Position\n"
			"	            + gl_TessCoord.y * gl_in[1].gl_Position\n"
			"	            + gl_TessCoord.y * gl_in[2].gl_Position\n"
			"	            + gl_TessCoord.z * gl_in[3].gl_Position;\n"
			"}\n";
}

class TessControlOutputVerticesCase : public TessProgramQueryCase
{
public:
						TessControlOutputVerticesCase	(Context& context, const char* name, const char* desc);
private:
	IterateResult		iterate							(void);
};

TessControlOutputVerticesCase::TessControlOutputVerticesCase (Context& context, const char* name, const char* desc)
	: TessProgramQueryCase(context, name, desc)
{
}

TessControlOutputVerticesCase::IterateResult TessControlOutputVerticesCase::iterate (void)
{
	checkTessellationSupport(m_context);

	glu::ShaderProgram program (m_context.getRenderContext(), glu::ProgramSources()
																<< glu::VertexSource(getVertexSource())
																<< glu::FragmentSource(getFragmentSource())
																<< glu::TessellationControlSource(getTessCtrlSource("layout(vertices=4) out"))
																<< glu::TessellationEvaluationSource(getTessEvalSource("layout(triangles) in")));

	m_testCtx.getLog() << program;
	if (!program.isOk())
		throw tcu::TestError("failed to build program");

	{
		glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
		tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");

		gl.enableLogging(true);
		verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_CONTROL_OUTPUT_VERTICES, 4, QUERY_PROGRAM_INTEGER);

		result.setTestContextResult(m_testCtx);
	}
	return STOP;
}

class TessGenModeQueryCase : public TessProgramQueryCase
{
public:
						TessGenModeQueryCase	(Context& context, const char* name, const char* desc);
private:
	IterateResult		iterate					(void);
};

TessGenModeQueryCase::TessGenModeQueryCase (Context& context, const char* name, const char* desc)
	: TessProgramQueryCase(context, name, desc)
{
}

TessGenModeQueryCase::IterateResult TessGenModeQueryCase::iterate (void)
{
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

	static const struct
	{
		const char* description;
		const char* layout;
		glw::GLenum mode;
	} s_modes[] =
	{
		{ "Triangles",	"layout(triangles) in",	GL_TRIANGLES	},
		{ "Isolines",	"layout(isolines) in",	GL_ISOLINES		},
		{ "Quads",		"layout(quads) in",		GL_QUADS		},
	};

	checkTessellationSupport(m_context);
	gl.enableLogging(true);

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx)
	{
		const tcu::ScopedLogSection	section(m_testCtx.getLog(), "Type", s_modes[ndx].description);

		glu::ShaderProgram program (m_context.getRenderContext(), glu::ProgramSources()
																	<< glu::VertexSource(getVertexSource())
																	<< glu::FragmentSource(getFragmentSource())
																	<< glu::TessellationControlSource(getTessCtrlSource("layout(vertices=6) out"))
																	<< glu::TessellationEvaluationSource(getTessEvalSource(s_modes[ndx].layout)));

		m_testCtx.getLog() << program;
		if (!program.isOk())
			result.fail("failed to build program");
		else
			verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_MODE, s_modes[ndx].mode, QUERY_PROGRAM_INTEGER);
	}

	result.setTestContextResult(m_testCtx);
	return STOP;
}

class TessGenSpacingQueryCase : public TessProgramQueryCase
{
public:
						TessGenSpacingQueryCase	(Context& context, const char* name, const char* desc);
private:
	IterateResult		iterate					(void);
};

TessGenSpacingQueryCase::TessGenSpacingQueryCase (Context& context, const char* name, const char* desc)
	: TessProgramQueryCase(context, name, desc)
{
}

TessGenSpacingQueryCase::IterateResult TessGenSpacingQueryCase::iterate (void)
{
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

	static const struct
	{
		const char* description;
		const char* layout;
		glw::GLenum spacing;
	} s_modes[] =
	{
		{ "Default spacing",			"layout(triangles) in",								GL_EQUAL			},
		{ "Equal spacing",				"layout(triangles, equal_spacing) in",				GL_EQUAL			},
		{ "Fractional even spacing",	"layout(triangles, fractional_even_spacing) in",	GL_FRACTIONAL_EVEN	},
		{ "Fractional odd spacing",		"layout(triangles, fractional_odd_spacing) in",		GL_FRACTIONAL_ODD	},
	};

	checkTessellationSupport(m_context);
	gl.enableLogging(true);

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx)
	{
		const tcu::ScopedLogSection	section(m_testCtx.getLog(), "Type", s_modes[ndx].description);

		glu::ShaderProgram program (m_context.getRenderContext(), glu::ProgramSources()
																	<< glu::VertexSource(getVertexSource())
																	<< glu::FragmentSource(getFragmentSource())
																	<< glu::TessellationControlSource(getTessCtrlSource("layout(vertices=6) out"))
																	<< glu::TessellationEvaluationSource(getTessEvalSource(s_modes[ndx].layout)));

		m_testCtx.getLog() << program;
		if (!program.isOk())
			result.fail("failed to build program");
		else
			verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_SPACING, s_modes[ndx].spacing, QUERY_PROGRAM_INTEGER);
	}

	result.setTestContextResult(m_testCtx);
	return STOP;
}

class TessGenVertexOrderQueryCase : public TessProgramQueryCase
{
public:
						TessGenVertexOrderQueryCase	(Context& context, const char* name, const char* desc);
private:
	IterateResult		iterate						(void);
};

TessGenVertexOrderQueryCase::TessGenVertexOrderQueryCase (Context& context, const char* name, const char* desc)
	: TessProgramQueryCase(context, name, desc)
{
}

TessGenVertexOrderQueryCase::IterateResult TessGenVertexOrderQueryCase::iterate (void)
{
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

	static const struct
	{
		const char* description;
		const char* layout;
		glw::GLenum order;
	} s_modes[] =
	{
		{ "Default order",	"layout(triangles) in",			GL_CCW	},
		{ "CW order",		"layout(triangles, cw) in",		GL_CW	},
		{ "CCW order",		"layout(triangles, ccw) in",	GL_CCW	},
	};

	checkTessellationSupport(m_context);
	gl.enableLogging(true);

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx)
	{
		const tcu::ScopedLogSection	section(m_testCtx.getLog(), "Type", s_modes[ndx].description);

		glu::ShaderProgram program (m_context.getRenderContext(), glu::ProgramSources()
																	<< glu::VertexSource(getVertexSource())
																	<< glu::FragmentSource(getFragmentSource())
																	<< glu::TessellationControlSource(getTessCtrlSource("layout(vertices=6) out"))
																	<< glu::TessellationEvaluationSource(getTessEvalSource(s_modes[ndx].layout)));

		m_testCtx.getLog() << program;
		if (!program.isOk())
			result.fail("failed to build program");
		else
			verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_VERTEX_ORDER, s_modes[ndx].order, QUERY_PROGRAM_INTEGER);
	}

	result.setTestContextResult(m_testCtx);
	return STOP;
}

class TessGenPointModeQueryCase : public TessProgramQueryCase
{
public:
						TessGenPointModeQueryCase	(Context& context, const char* name, const char* desc);
private:
	IterateResult		iterate						(void);
};

TessGenPointModeQueryCase::TessGenPointModeQueryCase (Context& context, const char* name, const char* desc)
	: TessProgramQueryCase(context, name, desc)
{
}

TessGenPointModeQueryCase::IterateResult TessGenPointModeQueryCase::iterate (void)
{
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

	static const struct
	{
		const char* description;
		const char* layout;
		glw::GLenum mode;
	} s_modes[] =
	{
		{ "Default mode",	"layout(triangles) in",			GL_FALSE	},
		{ "Point mode",		"layout(triangles, point_mode) in",		GL_TRUE		},
	};

	checkTessellationSupport(m_context);
	gl.enableLogging(true);

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_modes); ++ndx)
	{
		const tcu::ScopedLogSection	section(m_testCtx.getLog(), "Type", s_modes[ndx].description);

		glu::ShaderProgram program (m_context.getRenderContext(), glu::ProgramSources()
																	<< glu::VertexSource(getVertexSource())
																	<< glu::FragmentSource(getFragmentSource())
																	<< glu::TessellationControlSource(getTessCtrlSource("layout(vertices=6) out"))
																	<< glu::TessellationEvaluationSource(getTessEvalSource(s_modes[ndx].layout)));

		m_testCtx.getLog() << program;
		if (!program.isOk())
			result.fail("failed to build program");
		else
			verifyStateProgramInteger(result, gl, program.getProgram(), GL_TESS_GEN_POINT_MODE, s_modes[ndx].mode, QUERY_PROGRAM_INTEGER);
	}

	result.setTestContextResult(m_testCtx);
	return STOP;
}

class ReferencedByTessellationQueryCase : public TestCase
{
public:
					ReferencedByTessellationQueryCase	(Context& context, const char* name, const char* desc, bool isCtrlCase);
private:
	void			init								(void);
	IterateResult	iterate								(void);

	std::string		getVertexSource						(void) const;
	std::string		getFragmentSource					(void) const;
	std::string		getTessCtrlSource					(void) const;
	std::string		getTessEvalSource					(void) const;

	const bool		m_isCtrlCase;
};

ReferencedByTessellationQueryCase::ReferencedByTessellationQueryCase (Context& context, const char* name, const char* desc, bool isCtrlCase)
	: TestCase		(context, name, desc)
	, m_isCtrlCase	(isCtrlCase)
{
}

void ReferencedByTessellationQueryCase::init (void)
{
	checkTessellationSupport(m_context);
}

ReferencedByTessellationQueryCase::IterateResult ReferencedByTessellationQueryCase::iterate (void)
{
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	glu::ShaderProgram		program	(m_context.getRenderContext(), glu::ProgramSources()
																	<< glu::VertexSource(getVertexSource())
																	<< glu::FragmentSource(getFragmentSource())
																	<< glu::TessellationControlSource(getTessCtrlSource())
																	<< glu::TessellationEvaluationSource(getTessEvalSource()));

	gl.enableLogging(true);

	m_testCtx.getLog() << program;
	if (!program.isOk())
		result.fail("failed to build program");
	else
	{
		const deUint32 props[1] = { (deUint32)((m_isCtrlCase) ? (GL_REFERENCED_BY_TESS_CONTROL_SHADER) : (GL_REFERENCED_BY_TESS_EVALUATION_SHADER)) };

		{
			const tcu::ScopedLogSection section		(m_testCtx.getLog(), "UnreferencedUniform", "Unreferenced uniform u_unreferenced");
			deUint32					resourcePos;
			glw::GLsizei				length		= 0;
			glw::GLint					referenced	= 0;

			resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_unreferenced");
			m_testCtx.getLog() << tcu::TestLog::Message << "u_unreferenced resource index: " << resourcePos << tcu::TestLog::EndMessage;

			if (resourcePos == GL_INVALID_INDEX)
				result.fail("resourcePos was GL_INVALID_INDEX");
			else
			{
				gl.glGetProgramResourceiv(program.getProgram(), GL_UNIFORM, resourcePos, 1, props, 1, &length, &referenced);
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "Query " << glu::getProgramResourcePropertyStr(props[0])
					<< ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced)
					<< tcu::TestLog::EndMessage;

				GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "query resource");

				if (length == 0 || referenced != GL_FALSE)
					result.fail("expected GL_FALSE");
			}
		}

		{
			const tcu::ScopedLogSection section		(m_testCtx.getLog(), "ReferencedUniform", "Referenced uniform u_referenced");
			deUint32					resourcePos;
			glw::GLsizei				length		= 0;
			glw::GLint					referenced	= 0;

			resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_referenced");
			m_testCtx.getLog() << tcu::TestLog::Message << "u_referenced resource index: " << resourcePos << tcu::TestLog::EndMessage;

			if (resourcePos == GL_INVALID_INDEX)
				result.fail("resourcePos was GL_INVALID_INDEX");
			else
			{
				gl.glGetProgramResourceiv(program.getProgram(), GL_UNIFORM, resourcePos, 1, props, 1, &length, &referenced);
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "Query " << glu::getProgramResourcePropertyStr(props[0])
					<< ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced)
					<< tcu::TestLog::EndMessage;

				GLU_EXPECT_NO_ERROR(gl.glGetError(), "query resource");

				if (length == 0 || referenced != GL_TRUE)
					result.fail("expected GL_TRUE");
			}
		}
	}

	result.setTestContextResult(m_testCtx);
	return STOP;
}

std::string ReferencedByTessellationQueryCase::getVertexSource (void) const
{
	return	"#version 310 es\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = vec4(float(gl_VertexID), float(gl_VertexID / 2), 0.0, 1.0);\n"
			"}\n";
}

std::string ReferencedByTessellationQueryCase::getFragmentSource (void) const
{
	return	"#version 310 es\n"
			"layout (location = 0) out mediump vec4 o_color;\n"
			"void main (void)\n"
			"{\n"
			"	o_color = vec4(0.0, 1.0, 0.0, 1.0);\n"
			"}\n";
}

std::string ReferencedByTessellationQueryCase::getTessCtrlSource (void) const
{
	std::ostringstream buf;
	buf <<	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			"layout(vertices = 3) out;\n"
			"uniform highp vec4 " << ((m_isCtrlCase) ? ("u_referenced") : ("u_unreferenced")) << ";\n"
			"void main (void)\n"
			"{\n"
			"	vec4 offset = " << ((m_isCtrlCase) ? ("u_referenced") : ("u_unreferenced")) << ";\n"
			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position + offset;\n"
			"	gl_TessLevelInner[0] = 2.8;\n"
			"	gl_TessLevelInner[1] = 2.8;\n"
			"	gl_TessLevelOuter[0] = 2.8;\n"
			"	gl_TessLevelOuter[1] = 2.8;\n"
			"	gl_TessLevelOuter[2] = 2.8;\n"
			"	gl_TessLevelOuter[3] = 2.8;\n"
			"}\n";
	return buf.str();
}

std::string ReferencedByTessellationQueryCase::getTessEvalSource (void) const
{
	std::ostringstream buf;
	buf <<	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			"layout(triangles) in;\n"
			"uniform highp vec4 " << ((m_isCtrlCase) ? ("u_unreferenced") : ("u_referenced")) << ";\n"
			"void main (void)\n"
			"{\n"
			"	vec4 offset = " << ((m_isCtrlCase) ? ("u_unreferenced") : ("u_referenced")) << ";\n"
			"	gl_Position = gl_TessCoord.x * gl_in[0].gl_Position\n"
			"	            + gl_TessCoord.y * gl_in[1].gl_Position\n"
			"	            + gl_TessCoord.z * gl_in[2].gl_Position\n"
			"	            + offset;\n"
			"}\n";

	return buf.str();
}

class IsPerPatchQueryCase : public TestCase
{
public:
					IsPerPatchQueryCase		(Context& context, const char* name, const char* desc);
private:
	void			init					(void);
	IterateResult	iterate					(void);
};

IsPerPatchQueryCase::IsPerPatchQueryCase (Context& context, const char* name, const char* desc)
	: TestCase(context, name, desc)
{
}

void IsPerPatchQueryCase::init (void)
{
	checkTessellationSupport(m_context);
}

IsPerPatchQueryCase::IterateResult IsPerPatchQueryCase::iterate (void)
{
	static const char* const s_controlSource =	"#version 310 es\n"
												"#extension GL_EXT_tessellation_shader : require\n"
												"layout(vertices = 3) out;\n"
												"patch out highp vec4 v_perPatch;\n"
												"out highp vec4 v_perVertex[];\n"
												"void main (void)\n"
												"{\n"
												"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
												"	v_perPatch = gl_in[0].gl_Position;\n"
												"	v_perVertex[gl_InvocationID] = -gl_in[gl_InvocationID].gl_Position;\n"
												"	gl_TessLevelInner[0] = 2.8;\n"
												"	gl_TessLevelInner[1] = 2.8;\n"
												"	gl_TessLevelOuter[0] = 2.8;\n"
												"	gl_TessLevelOuter[1] = 2.8;\n"
												"	gl_TessLevelOuter[2] = 2.8;\n"
												"	gl_TessLevelOuter[3] = 2.8;\n"
												"}\n";
	tcu::ResultCollector	result	(m_testCtx.getLog(), " // ERROR: ");
	glu::CallLogWrapper		gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	glu::ShaderProgram		program	(m_context.getRenderContext(), glu::ProgramSources()
																	<< glu::TessellationControlSource(s_controlSource)
																	<< glu::ProgramSeparable(true));

	gl.enableLogging(true);

	m_testCtx.getLog() << program;
	if (!program.isOk())
		result.fail("failed to build program");
	else
	{
		const deUint32 props[1] = { GL_IS_PER_PATCH };

		{
			const tcu::ScopedLogSection section		(m_testCtx.getLog(), "PerPatchOutput", "Per patch v_perPatch");
			deUint32					resourcePos;
			glw::GLsizei				length		= 0;
			glw::GLint					referenced	= 0;

			resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_PROGRAM_OUTPUT, "v_perPatch");
			m_testCtx.getLog() << tcu::TestLog::Message << "v_perPatch resource index: " << resourcePos << tcu::TestLog::EndMessage;

			if (resourcePos == GL_INVALID_INDEX)
				result.fail("resourcePos was GL_INVALID_INDEX");
			else
			{
				gl.glGetProgramResourceiv(program.getProgram(), GL_PROGRAM_OUTPUT, resourcePos, 1, props, 1, &length, &referenced);
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "Query " << glu::getProgramResourcePropertyStr(props[0])
					<< ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced)
					<< tcu::TestLog::EndMessage;

				GLS_COLLECT_GL_ERROR(result, gl.glGetError(), "query resource");

				if (length == 0 || referenced != GL_TRUE)
					result.fail("expected GL_TRUE");
			}
		}

		{
			const tcu::ScopedLogSection section		(m_testCtx.getLog(), "PerVertexhOutput", "Per vertex v_perVertex");
			deUint32					resourcePos;
			glw::GLsizei				length		= 0;
			glw::GLint					referenced	= 0;

			resourcePos = gl.glGetProgramResourceIndex(program.getProgram(), GL_PROGRAM_OUTPUT, "v_perVertex");
			m_testCtx.getLog() << tcu::TestLog::Message << "v_perVertex resource index: " << resourcePos << tcu::TestLog::EndMessage;

			if (resourcePos == GL_INVALID_INDEX)
				result.fail("resourcePos was GL_INVALID_INDEX");
			else
			{
				gl.glGetProgramResourceiv(program.getProgram(), GL_PROGRAM_OUTPUT, resourcePos, 1, props, 1, &length, &referenced);
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "Query " << glu::getProgramResourcePropertyStr(props[0])
					<< ", got " << length << " value(s), value[0] = " << glu::getBooleanStr(referenced)
					<< tcu::TestLog::EndMessage;

				GLU_EXPECT_NO_ERROR(gl.glGetError(), "query resource");

				if (length == 0 || referenced != GL_FALSE)
					result.fail("expected GL_FALSE");
			}
		}
	}

	result.setTestContextResult(m_testCtx);
	return STOP;
}

} // anonymous

TessellationTests::TessellationTests (Context& context)
	: TestCaseGroup(context, "tessellation", "Tessellation Tests")
{
}

TessellationTests::~TessellationTests (void)
{
}

void TessellationTests::init (void)
{
	{
		tcu::TestCaseGroup* const queryGroup = new tcu::TestCaseGroup(m_testCtx, "state_query", "Query tests");
		addChild(queryGroup);

		// new limits
		queryGroup->addChild(new LimitQueryCase(m_context, "max_patch_vertices",								"Test MAX_PATCH_VERTICES",								GL_MAX_PATCH_VERTICES,							32));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_gen_level",								"Test MAX_TESS_GEN_LEVEL",								GL_MAX_TESS_GEN_LEVEL,							64));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_uniform_components",				"Test MAX_TESS_CONTROL_UNIFORM_COMPONENTS",				GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS,			1024));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_uniform_components",			"Test MAX_TESS_EVALUATION_UNIFORM_COMPONENTS",			GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS,		1024));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_texture_image_units",				"Test MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS",			GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS,		16));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_texture_image_units",			"Test MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS",			GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS,		16));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_output_components",				"Test MAX_TESS_CONTROL_OUTPUT_COMPONENTS",				GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS,			64));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_patch_components",							"Test MAX_TESS_PATCH_COMPONENTS",						GL_MAX_TESS_PATCH_COMPONENTS,					120));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_total_output_components",			"Test MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS",		GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS,	2048));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_output_components",				"Test MAX_TESS_EVALUATION_OUTPUT_COMPONENTS",			GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS,		64));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_uniform_blocks",					"Test MAX_TESS_CONTROL_UNIFORM_BLOCKS",					GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS,				12));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_uniform_blocks",				"Test MAX_TESS_EVALUATION_UNIFORM_BLOCKS",				GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS,			12));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_input_components",					"Test MAX_TESS_CONTROL_INPUT_COMPONENTS",				GL_MAX_TESS_CONTROL_INPUT_COMPONENTS,			64));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_input_components",				"Test MAX_TESS_EVALUATION_INPUT_COMPONENTS",			GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS,		64));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_atomic_counter_buffers",			"Test MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS",			GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS,		0));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_atomic_counter_buffers",		"Test MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS",		GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS,	0));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_atomic_counters",					"Test MAX_TESS_CONTROL_ATOMIC_COUNTERS",				GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS,			0));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_atomic_counters",				"Test MAX_TESS_EVALUATION_ATOMIC_COUNTERS",				GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS,			0));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_image_uniforms",					"Test MAX_TESS_CONTROL_IMAGE_UNIFORMS",					GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS,				0));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_image_uniforms",				"Test MAX_TESS_EVALUATION_IMAGE_UNIFORMS",				GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS,			0));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_control_shader_storage_blocks",			"Test MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS",			GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS,		0));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_tess_evaluation_shader_storage_blocks",			"Test MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS",		GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS,	0));

		// modified limits
		queryGroup->addChild(new LimitQueryCase(m_context, "max_uniform_buffer_bindings",						"Test MAX_UNIFORM_BUFFER_BINDINGS",						GL_MAX_UNIFORM_BUFFER_BINDINGS,					72));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_combined_uniform_blocks",						"Test MAX_COMBINED_UNIFORM_BLOCKS",						GL_MAX_COMBINED_UNIFORM_BLOCKS,					60));
		queryGroup->addChild(new LimitQueryCase(m_context, "max_combined_texture_image_units",					"Test MAX_COMBINED_TEXTURE_IMAGE_UNITS",				GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,			96));

		// combined limits
		queryGroup->addChild(new CombinedUniformLimitCase(m_context, "max_combined_tess_control_uniform_components",		"Test MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS",	GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS,		GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS,		GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS));
		queryGroup->addChild(new CombinedUniformLimitCase(m_context, "max_combined_tess_evaluation_uniform_components",		"Test MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS",	GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS,		GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS,	GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS));

		// features
		queryGroup->addChild(new PrimitiveRestartForPatchesSupportedCase(m_context, "primitive_restart_for_patches_supported", "Test PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED"));

		// states
		queryGroup->addChild(new PatchVerticesStateCase(m_context, "patch_vertices", "Test PATCH_VERTICES"));

		// program states
		queryGroup->addChild(new TessControlOutputVerticesCase	(m_context, "tess_control_output_vertices",	"Test TESS_CONTROL_OUTPUT_VERTICES"));
		queryGroup->addChild(new TessGenModeQueryCase			(m_context, "tess_gen_mode",				"Test TESS_GEN_MODE"));
		queryGroup->addChild(new TessGenSpacingQueryCase		(m_context, "tess_gen_spacing",				"Test TESS_GEN_SPACING"));
		queryGroup->addChild(new TessGenVertexOrderQueryCase	(m_context, "tess_gen_vertex_order",		"Test TESS_GEN_VERTEX_ORDER"));
		queryGroup->addChild(new TessGenPointModeQueryCase		(m_context, "tess_gen_point_mode",			"Test TESS_GEN_POINT_MODE"));

		// resource queries
		queryGroup->addChild(new ReferencedByTessellationQueryCase	(m_context, "referenced_by_tess_control_shader",	"Test REFERENCED_BY_TESS_CONTROL_SHADER",		true));
		queryGroup->addChild(new ReferencedByTessellationQueryCase	(m_context, "referenced_by_tess_evaluation_shader",	"Test REFERENCED_BY_TESS_EVALUATION_SHADER",	false));
		queryGroup->addChild(new IsPerPatchQueryCase				(m_context, "is_per_patch",							"Test IS_PER_PATCH"));
	}

	{
		TestCaseGroup* const tessCoordGroup = new TestCaseGroup(m_context, "tesscoord", "Get tessellation coordinates with transform feedback and validate them");
		addChild(tessCoordGroup);

		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
		{
			const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;

			for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
				tessCoordGroup->addChild(new TessCoordCase(m_context,
														   (string() + getTessPrimitiveTypeShaderName(primitiveType) + "_" + getSpacingModeShaderName((SpacingMode)spacingI)).c_str(), "",
														   primitiveType, (SpacingMode)spacingI));
		}
	}

	{
		TestCaseGroup* const windingGroup = new TestCaseGroup(m_context, "winding", "Test the cw and ccw input layout qualifiers");
		addChild(windingGroup);

		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
		{
			const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
			if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
				continue;

			for (int windingI = 0; windingI < WINDING_LAST; windingI++)
			{
				const Winding winding = (Winding)windingI;
				windingGroup->addChild(new WindingCase(m_context, (string() + getTessPrimitiveTypeShaderName(primitiveType) + "_" + getWindingShaderName(winding)).c_str(), "", primitiveType, winding));
			}
		}
	}

	{
		TestCaseGroup* const shaderInputOutputGroup = new TestCaseGroup(m_context, "shader_input_output", "Test tessellation control and evaluation shader inputs and outputs");
		addChild(shaderInputOutputGroup);

		{
			static const struct
			{
				int inPatchSize;
				int outPatchSize;
			} patchVertexCountCases[] =
			{
				{  5, 10 },
				{ 10,  5 }
			};

			for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(patchVertexCountCases); caseNdx++)
			{
				const int inSize	= patchVertexCountCases[caseNdx].inPatchSize;
				const int outSize	= patchVertexCountCases[caseNdx].outPatchSize;

				const string caseName = "patch_vertices_" + de::toString(inSize) + "_in_" + de::toString(outSize) + "_out";

				shaderInputOutputGroup->addChild(new PatchVertexCountCase(m_context, caseName.c_str(), "Test input and output patch vertex counts", inSize, outSize,
																		  ("data/tessellation/" + caseName + "_ref.png").c_str()));
			}
		}

		for (int caseTypeI = 0; caseTypeI < PerPatchDataCase::CASETYPE_LAST; caseTypeI++)
		{
			const PerPatchDataCase::CaseType	caseType	= (PerPatchDataCase::CaseType)caseTypeI;
			const char* const					caseName	= PerPatchDataCase::getCaseTypeName(caseType);

			shaderInputOutputGroup->addChild(new PerPatchDataCase(m_context, caseName, PerPatchDataCase::getCaseTypeDescription(caseType), caseType,
																  PerPatchDataCase::caseTypeUsesRefImageFromFile(caseType) ? (string() + "data/tessellation/" + caseName + "_ref.png").c_str() : DE_NULL));
		}

		for (int caseTypeI = 0; caseTypeI < GLPositionCase::CASETYPE_LAST; caseTypeI++)
		{
			const GLPositionCase::CaseType	caseType	= (GLPositionCase::CaseType)caseTypeI;
			const char* const				caseName	= GLPositionCase::getCaseTypeName(caseType);

			shaderInputOutputGroup->addChild(new GLPositionCase(m_context, caseName, "", caseType, "data/tessellation/gl_position_ref.png"));
		}

		shaderInputOutputGroup->addChild(new BarrierCase(m_context, "barrier", "Basic barrier usage", "data/tessellation/barrier_ref.png"));
	}

	{
		TestCaseGroup* const miscDrawGroup = new TestCaseGroup(m_context, "misc_draw", "Miscellaneous draw-result-verifying cases");
		addChild(miscDrawGroup);

		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
		{
			const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
			if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
				continue;

			const char* const primTypeName = getTessPrimitiveTypeShaderName(primitiveType);

			for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
			{
				const string caseName = string() + "fill_cover_" + primTypeName + "_" + getSpacingModeShaderName((SpacingMode)spacingI);

				miscDrawGroup->addChild(new BasicTriangleFillCoverCase(m_context,
																	   caseName.c_str(), "Check that there are no obvious gaps in the triangle-filled area of a tessellated shape",
																	   primitiveType, (SpacingMode)spacingI,
																	   ("data/tessellation/" + caseName + "_ref").c_str()));
			}
		}

		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
		{
			const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
			if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
				continue;

			const char* const primTypeName = getTessPrimitiveTypeShaderName(primitiveType);

			for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
			{
				const string caseName = string() + "fill_overlap_" + primTypeName + "_" + getSpacingModeShaderName((SpacingMode)spacingI);

				miscDrawGroup->addChild(new BasicTriangleFillNonOverlapCase(m_context,
																			caseName.c_str(), "Check that there are no obvious triangle overlaps in the triangle-filled area of a tessellated shape",
																			primitiveType, (SpacingMode)spacingI,
																			("data/tessellation/" + caseName + "_ref").c_str()));
			}
		}

		for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
		{
			const string caseName = string() + "isolines_" + getSpacingModeShaderName((SpacingMode)spacingI);

			miscDrawGroup->addChild(new IsolinesRenderCase(m_context,
														   caseName.c_str(), "Basic isolines render test",
														   (SpacingMode)spacingI,
														   ("data/tessellation/" + caseName + "_ref").c_str()));
		}
	}

	{
		TestCaseGroup* const commonEdgeGroup = new TestCaseGroup(m_context, "common_edge", "Draw multiple adjacent shapes and check that no cracks appear between them");
		addChild(commonEdgeGroup);

		for (int caseTypeI = 0; caseTypeI < CommonEdgeCase::CASETYPE_LAST; caseTypeI++)
		{
			for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
			{
				const CommonEdgeCase::CaseType	caseType		= (CommonEdgeCase::CaseType)caseTypeI;
				const TessPrimitiveType			primitiveType	= (TessPrimitiveType)primitiveTypeI;
				if (primitiveType == TESSPRIMITIVETYPE_ISOLINES)
						continue;

				for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
				{
					const SpacingMode	spacing		= (SpacingMode)spacingI;
					const string		caseName	= (string() + getTessPrimitiveTypeShaderName(primitiveType)
																+ "_" + getSpacingModeShaderName(spacing)
																+ (caseType == CommonEdgeCase::CASETYPE_BASIC		? ""
																 : caseType == CommonEdgeCase::CASETYPE_PRECISE		? "_precise"
																 : DE_NULL));

					commonEdgeGroup->addChild(new CommonEdgeCase(m_context, caseName.c_str(), "", primitiveType, spacing, caseType));
				}
			}
		}
	}

	{
		TestCaseGroup* const fractionalSpacingModeGroup = new TestCaseGroup(m_context, "fractional_spacing", "Test fractional spacing modes");
		addChild(fractionalSpacingModeGroup);

		fractionalSpacingModeGroup->addChild(new FractionalSpacingModeCase(m_context, "odd",	"", SPACINGMODE_FRACTIONAL_ODD));
		fractionalSpacingModeGroup->addChild(new FractionalSpacingModeCase(m_context, "even",	"", SPACINGMODE_FRACTIONAL_EVEN));
	}

	{
		TestCaseGroup* const primitiveDiscardGroup = new TestCaseGroup(m_context, "primitive_discard", "Test primitive discard with relevant outer tessellation level <= 0.0");
		addChild(primitiveDiscardGroup);

		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
		{
			for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
			{
				for (int windingI = 0; windingI < WINDING_LAST; windingI++)
				{
					for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
					{
						const TessPrimitiveType		primitiveType	= (TessPrimitiveType)primitiveTypeI;
						const SpacingMode			spacing			= (SpacingMode)spacingI;
						const Winding				winding			= (Winding)windingI;
						const bool					usePointMode	= usePointModeI != 0;

						primitiveDiscardGroup->addChild(new PrimitiveDiscardCase(m_context, (string() + getTessPrimitiveTypeShaderName(primitiveType)
																									  + "_" + getSpacingModeShaderName(spacing)
																									  + "_" + getWindingShaderName(winding)
																									  + (usePointMode ? "_point_mode" : "")).c_str(), "",
																				 primitiveType, spacing, winding, usePointMode));
					}
				}
			}
		}
	}

	{
		TestCaseGroup* const invarianceGroup							= new TestCaseGroup(m_context, "invariance",						"Test tessellation invariance rules");

		TestCaseGroup* const invariantPrimitiveSetGroup					= new TestCaseGroup(m_context, "primitive_set",						"Test invariance rule #1");
		TestCaseGroup* const invariantOuterEdgeGroup					= new TestCaseGroup(m_context, "outer_edge_division",				"Test invariance rule #2");
		TestCaseGroup* const symmetricOuterEdgeGroup					= new TestCaseGroup(m_context, "outer_edge_symmetry",				"Test invariance rule #3");
		TestCaseGroup* const outerEdgeVertexSetIndexIndependenceGroup	= new TestCaseGroup(m_context, "outer_edge_index_independence",		"Test invariance rule #4");
		TestCaseGroup* const invariantTriangleSetGroup					= new TestCaseGroup(m_context, "triangle_set",						"Test invariance rule #5");
		TestCaseGroup* const invariantInnerTriangleSetGroup				= new TestCaseGroup(m_context, "inner_triangle_set",				"Test invariance rule #6");
		TestCaseGroup* const invariantOuterTriangleSetGroup				= new TestCaseGroup(m_context, "outer_triangle_set",				"Test invariance rule #7");
		TestCaseGroup* const tessCoordComponentRangeGroup				= new TestCaseGroup(m_context, "tess_coord_component_range",		"Test invariance rule #8, first part");
		TestCaseGroup* const oneMinusTessCoordComponentGroup			= new TestCaseGroup(m_context, "one_minus_tess_coord_component",	"Test invariance rule #8, second part");

		addChild(invarianceGroup);
		invarianceGroup->addChild(invariantPrimitiveSetGroup);
		invarianceGroup->addChild(invariantOuterEdgeGroup);
		invarianceGroup->addChild(symmetricOuterEdgeGroup);
		invarianceGroup->addChild(outerEdgeVertexSetIndexIndependenceGroup);
		invarianceGroup->addChild(invariantTriangleSetGroup);
		invarianceGroup->addChild(invariantInnerTriangleSetGroup);
		invarianceGroup->addChild(invariantOuterTriangleSetGroup);
		invarianceGroup->addChild(tessCoordComponentRangeGroup);
		invarianceGroup->addChild(oneMinusTessCoordComponentGroup);

		for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
		{
			const TessPrimitiveType		primitiveType	= (TessPrimitiveType)primitiveTypeI;
			const string				primName		= getTessPrimitiveTypeShaderName(primitiveType);
			const bool					triOrQuad		= primitiveType == TESSPRIMITIVETYPE_TRIANGLES || primitiveType == TESSPRIMITIVETYPE_QUADS;

			for (int spacingI = 0; spacingI < SPACINGMODE_LAST; spacingI++)
			{
				const SpacingMode	spacing			= (SpacingMode)spacingI;
				const string		primSpacName	= primName + "_" + getSpacingModeShaderName(spacing);

				if (triOrQuad)
				{
					invariantOuterEdgeGroup->addChild		(new InvariantOuterEdgeCase			(m_context, primSpacName.c_str(), "", primitiveType, spacing));
					invariantTriangleSetGroup->addChild		(new InvariantTriangleSetCase		(m_context, primSpacName.c_str(), "", primitiveType, spacing));
					invariantInnerTriangleSetGroup->addChild(new InvariantInnerTriangleSetCase	(m_context, primSpacName.c_str(), "", primitiveType, spacing));
					invariantOuterTriangleSetGroup->addChild(new InvariantOuterTriangleSetCase	(m_context, primSpacName.c_str(), "", primitiveType, spacing));
				}

				for (int windingI = 0; windingI < WINDING_LAST; windingI++)
				{
					const Winding	winding				= (Winding)windingI;
					const string	primSpacWindName	= primSpacName + "_" + getWindingShaderName(winding);

					for (int usePointModeI = 0; usePointModeI <= 1; usePointModeI++)
					{
						const bool		usePointMode			= usePointModeI != 0;
						const string	primSpacWindPointName	= primSpacWindName + (usePointMode ? "_point_mode" : "");

						invariantPrimitiveSetGroup->addChild		(new InvariantPrimitiveSetCase			(m_context, primSpacWindPointName.c_str(), "", primitiveType, spacing, winding, usePointMode));
						symmetricOuterEdgeGroup->addChild			(new SymmetricOuterEdgeCase				(m_context, primSpacWindPointName.c_str(), "", primitiveType, spacing, winding, usePointMode));
						tessCoordComponentRangeGroup->addChild		(new TessCoordComponentRangeCase		(m_context, primSpacWindPointName.c_str(), "", primitiveType, spacing, winding, usePointMode));
						oneMinusTessCoordComponentGroup->addChild	(new OneMinusTessCoordComponentCase		(m_context, primSpacWindPointName.c_str(), "", primitiveType, spacing, winding, usePointMode));

						if (triOrQuad)
							outerEdgeVertexSetIndexIndependenceGroup->addChild(new OuterEdgeVertexSetIndexIndependenceCase(m_context, primSpacWindPointName.c_str(), "",
																														   primitiveType, spacing, winding, usePointMode));
					}
				}
			}
		}
	}

	{
		static const struct
		{
			const char*					name;
			const char*					description;
			UserDefinedIOCase::IOType	ioType;
		} ioCases[] =
		{
			{ "per_patch",					"Per-patch TCS outputs",					UserDefinedIOCase::IO_TYPE_PER_PATCH				},
			{ "per_patch_array",			"Per-patch array TCS outputs",				UserDefinedIOCase::IO_TYPE_PER_PATCH_ARRAY			},
			{ "per_patch_block",			"Per-patch TCS outputs in IO block",		UserDefinedIOCase::IO_TYPE_PER_PATCH_BLOCK			},
			{ "per_patch_block_array",		"Per-patch TCS outputs in IO block array",	UserDefinedIOCase::IO_TYPE_PER_PATCH_BLOCK_ARRAY	},
			{ "per_vertex",					"Per-vertex TCS outputs",					UserDefinedIOCase::IO_TYPE_PER_VERTEX				},
			{ "per_vertex_block",			"Per-vertex TCS outputs in IO block",		UserDefinedIOCase::IO_TYPE_PER_VERTEX_BLOCK			},
		};

		TestCaseGroup* const userDefinedIOGroup = new TestCaseGroup(m_context, "user_defined_io", "Test non-built-in per-patch and per-vertex inputs and outputs");
		addChild(userDefinedIOGroup);

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(ioCases); ++ndx)
		{
			TestCaseGroup* const ioTypeGroup = new TestCaseGroup(m_context, ioCases[ndx].name, ioCases[ndx].description);
			userDefinedIOGroup->addChild(ioTypeGroup);

			for (int vertexArraySizeI = 0; vertexArraySizeI < UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_LAST; vertexArraySizeI++)
			{
				const UserDefinedIOCase::VertexIOArraySize	vertexArraySize			= (UserDefinedIOCase::VertexIOArraySize)vertexArraySizeI;
				TestCaseGroup* const						vertexArraySizeGroup	= new TestCaseGroup(m_context,
																										vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_IMPLICIT
																											? "vertex_io_array_size_implicit"
																									  : vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_EXPLICIT_SHADER_BUILTIN
																											? "vertex_io_array_size_shader_builtin"
																									  : vertexArraySizeI == UserDefinedIOCase::VERTEX_IO_ARRAY_SIZE_EXPLICIT_QUERY
																											? "vertex_io_array_size_query"
																									  : DE_NULL,
																									    "");
				ioTypeGroup->addChild(vertexArraySizeGroup);

				for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
				{
					const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
					vertexArraySizeGroup->addChild(new UserDefinedIOCase(m_context, getTessPrimitiveTypeShaderName(primitiveType), "", primitiveType, ioCases[ndx].ioType, vertexArraySize, UserDefinedIOCase::TESS_CONTROL_OUT_ARRAY_SIZE_IMPLICIT,
																		 (string() + "data/tessellation/user_defined_io_" + getTessPrimitiveTypeShaderName(primitiveType) + "_ref.png").c_str()));
				}

				if (ioCases[ndx].ioType == UserDefinedIOCase::IO_TYPE_PER_VERTEX
					|| ioCases[ndx].ioType == UserDefinedIOCase::IO_TYPE_PER_VERTEX_BLOCK)
				{
					for (int primitiveTypeI = 0; primitiveTypeI < TESSPRIMITIVETYPE_LAST; primitiveTypeI++)
					{
						const TessPrimitiveType primitiveType = (TessPrimitiveType)primitiveTypeI;
						vertexArraySizeGroup->addChild(new UserDefinedIOCase(m_context, (string(getTessPrimitiveTypeShaderName(primitiveType)) + "_explicit_tcs_out_size").c_str(), "", primitiveType, ioCases[ndx].ioType, vertexArraySize, UserDefinedIOCase::TESS_CONTROL_OUT_ARRAY_SIZE_LAYOUT,
																			 (string() + "data/tessellation/user_defined_io_" + getTessPrimitiveTypeShaderName(primitiveType) + "_ref.png").c_str()));
					}
				}
			}
		}

		{
			TestCaseGroup* const				negativeGroup	= new TestCaseGroup(m_context, "negative", "Negative cases");
			gls::ShaderLibrary					shaderLibrary	(m_testCtx, m_context.getRenderContext(), m_context.getContextInfo());
			const std::vector<tcu::TestNode*>	children		= shaderLibrary.loadShaderFile("shaders/tessellation_negative_user_defined_io.test");

			userDefinedIOGroup->addChild(negativeGroup);

			for (int i = 0; i < (int)children.size(); i++)
				negativeGroup->addChild(children[i]);
		}
	}
}

} // Functional
} // gles31
} // deqp