/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 2.0 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 Shader loop tests.
 *
 * \todo [petri]
 * - loop body cases (do different operations inside the loops)
 * - more complex nested loops
 *   * random generated?
 *   * dataflow variations
 *   * mixed loop types
 * -
 *//*--------------------------------------------------------------------*/

#include "es2fShaderLoopTests.hpp"
#include "glsShaderLibrary.hpp"
#include "glsShaderRenderCase.hpp"
#include "gluShaderUtil.hpp"
#include "tcuStringTemplate.hpp"

#include "deStringUtil.hpp"
#include "deInt32.h"
#include "deMemory.h"

#include <map>

using namespace std;
using namespace tcu;
using namespace glu;
using namespace deqp::gls;

namespace deqp
{
namespace gles2
{
namespace Functional
{

// Repeated with for, while, do-while. Examples given as 'for' loops.
// Repeated for const, uniform, dynamic loops.
enum LoopCase
{
	LOOPCASE_EMPTY_BODY = 0,								// for (...) { }
	LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST,		// for (...) { break; <body>; }
	LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST,		// for (...) { <body>; break; }
	LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK,				// for (...) { <body>; if (cond) break; }
	LOOPCASE_SINGLE_STATEMENT,								// for (...) statement;
	LOOPCASE_COMPOUND_STATEMENT,							// for (...) { statement; statement; }
	LOOPCASE_SEQUENCE_STATEMENT,							// for (...) statement, statement;
	LOOPCASE_NO_ITERATIONS,									// for (i=0; i<0; i++) ...
	LOOPCASE_SINGLE_ITERATION,								// for (i=0; i<1; i++) ...
	LOOPCASE_SELECT_ITERATION_COUNT,						// for (i=0; i<a?b:c; i++) ...
	LOOPCASE_CONDITIONAL_CONTINUE,							// for (...) { if (cond) continue; }
	LOOPCASE_UNCONDITIONAL_CONTINUE,						// for (...) { <body>; continue; }
	LOOPCASE_ONLY_CONTINUE,									// for (...) { continue; }
	LOOPCASE_DOUBLE_CONTINUE,								// for (...) { if (cond) continue; <body>; continue; }
	LOOPCASE_CONDITIONAL_BREAK,								// for (...) { if (cond) break; }
	LOOPCASE_UNCONDITIONAL_BREAK,							// for (...) { <body>; break; }
	LOOPCASE_PRE_INCREMENT,									// for (...; ++i) { <body>; }
	LOOPCASE_POST_INCREMENT,								// for (...; i++) { <body>; }
	LOOPCASE_MIXED_BREAK_CONTINUE,
	LOOPCASE_VECTOR_COUNTER,								// for (ivec3 ndx = ...; ndx.x < ndx.y; ndx.x += ndx.z) { ... }
	LOOPCASE_101_ITERATIONS,								// loop for 101 iterations
	LOOPCASE_SEQUENCE,										// two loops in sequence
	LOOPCASE_NESTED,										// two nested loops
	LOOPCASE_NESTED_SEQUENCE,								// two loops in sequence nested inside a third
	LOOPCASE_NESTED_TRICKY_DATAFLOW_1,						// nested loops with tricky data flow
	LOOPCASE_NESTED_TRICKY_DATAFLOW_2,						// nested loops with tricky data flow
	LOOPCASE_CONDITIONAL_BODY,								// conditional body in loop
	LOOPCASE_FUNCTION_CALL_RETURN,							// function call in loop with return value usage
	LOOPCASE_FUNCTION_CALL_INOUT,							// function call with inout parameter usage

	LOOPCASE_LAST
};

enum LoopRequirement
{
	LOOPREQUIREMENT_STANDARD = 0,		//!< Minimum requirements by standard (constant for loop with simple iterator).
	LOOPREQUIREMENT_UNIFORM,
	LOOPREQUIREMENT_DYNAMIC,

	LOOPREQUIREMENT_LAST
};

static const char* getLoopCaseName (LoopCase loopCase)
{
	static const char* s_names[] =
	{
		"empty_body",
		"infinite_with_unconditional_break_first",
		"infinite_with_unconditional_break_last",
		"infinite_with_conditional_break",
		"single_statement",
		"compound_statement",
		"sequence_statement",
		"no_iterations",
		"single_iteration",
		"select_iteration_count",
		"conditional_continue",
		"unconditional_continue",
		"only_continue",
		"double_continue",
		"conditional_break",
		"unconditional_break",
		"pre_increment",
		"post_increment",
		"mixed_break_continue",
		"vector_counter",
		"101_iterations",
		"sequence",
		"nested",
		"nested_sequence",
		"nested_tricky_dataflow_1",
		"nested_tricky_dataflow_2",
		"conditional_body",
		"function_call_return",
		"function_call_inout"
	};

	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == LOOPCASE_LAST);
	DE_ASSERT(deInBounds32((int)loopCase, 0, LOOPCASE_LAST));
	return s_names[(int)loopCase];
}

enum LoopType
{
	LOOPTYPE_FOR = 0,
	LOOPTYPE_WHILE,
	LOOPTYPE_DO_WHILE,

	LOOPTYPE_LAST
};

static const char* getLoopTypeName (LoopType loopType)
{
	static const char* s_names[] =
	{
		"for",
		"while",
		"do_while"
	};

	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == LOOPTYPE_LAST);
	DE_ASSERT(deInBounds32((int)loopType, 0, LOOPTYPE_LAST));
	return s_names[(int)loopType];
}

enum LoopCountType
{
	LOOPCOUNT_CONSTANT = 0,
	LOOPCOUNT_UNIFORM,
	LOOPCOUNT_DYNAMIC,

	LOOPCOUNT_LAST
};

static const char* getLoopCountTypeName (LoopCountType countType)
{
	static const char* s_names[] =
	{
		"constant",
		"uniform",
		"dynamic"
	};

	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_names) == LOOPCOUNT_LAST);
	DE_ASSERT(deInBounds32((int)countType, 0, LOOPCOUNT_LAST));
	return s_names[(int)countType];
}

static void evalLoop0Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(0,1,2); }
static void evalLoop1Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(1,2,3); }
static void evalLoop2Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(2,3,0); }
static void evalLoop3Iters	(ShaderEvalContext& c) { c.color.xyz()	= c.coords.swizzle(3,0,1); }

static ShaderEvalFunc getLoopEvalFunc (int numIters)
{
	switch (numIters % 4)
	{
		case 0: return evalLoop0Iters;
		case 1:	return evalLoop1Iters;
		case 2:	return evalLoop2Iters;
		case 3:	return evalLoop3Iters;
	}

	DE_FATAL("Invalid loop iteration count.");
	return NULL;
}

// ShaderLoopCase

class ShaderLoopCase : public ShaderRenderCase
{
public:
								ShaderLoopCase			(Context& context, const char* name, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, LoopRequirement requirement, const char* vertShaderSource, const char* fragShaderSource);
	virtual						~ShaderLoopCase			(void);

	void						init					(void);

private:
								ShaderLoopCase			(const ShaderLoopCase&);	// not allowed!
	ShaderLoopCase&				operator=				(const ShaderLoopCase&);	// not allowed!

	virtual void				setup					(int programID);
	virtual void				setupUniforms			(int programID, const Vec4& constCoords);

	LoopRequirement				m_requirement;
};

ShaderLoopCase::ShaderLoopCase (Context& context, const char* name, const char* description, bool isVertexCase, ShaderEvalFunc evalFunc, LoopRequirement requirement, const char* vertShaderSource, const char* fragShaderSource)
	: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, description, isVertexCase, evalFunc)
	, m_requirement		(requirement)
{
	m_vertShaderSource	= vertShaderSource;
	m_fragShaderSource	= fragShaderSource;
}

ShaderLoopCase::~ShaderLoopCase (void)
{
}

void ShaderLoopCase::init (void)
{
	bool isSupported = true;

	if (m_requirement == LOOPREQUIREMENT_UNIFORM)
		isSupported = m_isVertexCase ? m_ctxInfo.isVertexUniformLoopSupported()
									 : m_ctxInfo.isFragmentUniformLoopSupported();
	else if (m_requirement == LOOPREQUIREMENT_DYNAMIC)
		isSupported = m_isVertexCase ? m_ctxInfo.isVertexDynamicLoopSupported()
									 : m_ctxInfo.isFragmentDynamicLoopSupported();

	try
	{
		ShaderRenderCase::init();
	}
	catch (const CompileFailed&)
	{
		if (!isSupported)
			throw tcu::NotSupportedError("Loop type is not supported");
		else
			throw;
	}
}

void ShaderLoopCase::setup (int programID)
{
	DE_UNREF(programID);
}

void ShaderLoopCase::setupUniforms (int programID, const Vec4& constCoords)
{
	DE_UNREF(programID);
	DE_UNREF(constCoords);
}

// Test case creation.

static ShaderLoopCase* createGenericLoopCase (Context& context, const char* caseName, const char* description, bool isVertexCase, LoopType loopType, LoopCountType loopCountType, Precision loopCountPrecision, DataType loopCountDataType)
{
	std::ostringstream vtx;
	std::ostringstream frag;
	std::ostringstream& op = isVertexCase ? vtx : frag;

	vtx << "attribute highp vec4 a_position;\n";
	vtx << "attribute highp vec4 a_coords;\n";

	if (loopCountType == LOOPCOUNT_DYNAMIC)
		vtx << "attribute mediump float a_one;\n";

	if (isVertexCase)
	{
		vtx << "varying mediump vec3 v_color;\n";
		frag << "varying mediump vec3 v_color;\n";
	}
	else
	{
		vtx << "varying mediump vec4 v_coords;\n";
		frag << "varying mediump vec4 v_coords;\n";

		if (loopCountType == LOOPCOUNT_DYNAMIC)
		{
			vtx << "varying mediump float v_one;\n";
			frag << "varying mediump float v_one;\n";
		}
	}

	// \todo [petri] Pass numLoopIters from outside?
	int		numLoopIters = 3;
	bool	isIntCounter = isDataTypeIntOrIVec(loopCountDataType);

	if (isIntCounter)
	{
		if (loopCountType == LOOPCOUNT_UNIFORM || loopCountType == LOOPCOUNT_DYNAMIC)
			op << "uniform ${COUNTER_PRECISION} int " << getIntUniformName(numLoopIters) << ";\n";
	}
	else
	{
		if (loopCountType == LOOPCOUNT_UNIFORM || loopCountType == LOOPCOUNT_DYNAMIC)
			op << "uniform ${COUNTER_PRECISION} float " << getFloatFractionUniformName(numLoopIters) << ";\n";

		if (numLoopIters != 1)
			op << "uniform ${COUNTER_PRECISION} float uf_one;\n";
	}

	vtx << "\n";
	vtx << "void main()\n";
	vtx << "{\n";
	vtx << "	gl_Position = a_position;\n";

	frag << "\n";
	frag << "void main()\n";
	frag << "{\n";

	if (isVertexCase)
		vtx << "	${PRECISION} vec4 coords = a_coords;\n";
	else
		frag << "	${PRECISION} vec4 coords = v_coords;\n";

	if (loopCountType == LOOPCOUNT_DYNAMIC)
	{
		if (isIntCounter)
		{
			if (isVertexCase)
				vtx << "	${COUNTER_PRECISION} int one = int(a_one + 0.5);\n";
			else
				frag << "	${COUNTER_PRECISION} int one = int(v_one + 0.5);\n";
		}
		else
		{
			if (isVertexCase)
				vtx << "	${COUNTER_PRECISION} float one = a_one;\n";
			else
				frag << "	${COUNTER_PRECISION} float one = v_one;\n";
		}
	}

	// Read array.
	op << "	${PRECISION} vec4 res = coords;\n";

	// Loop iteration count.
	string	iterMaxStr;

	if (isIntCounter)
	{
		if (loopCountType == LOOPCOUNT_CONSTANT)
			iterMaxStr = de::toString(numLoopIters);
		else if (loopCountType == LOOPCOUNT_UNIFORM)
			iterMaxStr = getIntUniformName(numLoopIters);
		else if (loopCountType == LOOPCOUNT_DYNAMIC)
			iterMaxStr = string(getIntUniformName(numLoopIters)) + "*one";
		else
			DE_ASSERT(false);
	}
	else
	{
		if (loopCountType == LOOPCOUNT_CONSTANT)
			iterMaxStr = "1.0";
		else if (loopCountType == LOOPCOUNT_UNIFORM)
			iterMaxStr = "uf_one";
		else if (loopCountType == LOOPCOUNT_DYNAMIC)
			iterMaxStr = "uf_one*one";
		else
			DE_ASSERT(false);
	}

	// Loop operations.
	string initValue		= isIntCounter ? "0" : "0.05";
	string loopCountDeclStr	= "${COUNTER_PRECISION} ${LOOP_VAR_TYPE} ndx = " + initValue;
	string loopCmpStr		= ("ndx < " + iterMaxStr);
	string incrementStr;
	if (isIntCounter)
		incrementStr = "ndx++";
	else
	{
		if (loopCountType == LOOPCOUNT_CONSTANT)
			incrementStr = string("ndx += ") + de::toString(1.0f / (float)numLoopIters);
		else if (loopCountType == LOOPCOUNT_UNIFORM)
			incrementStr = string("ndx += ") + getFloatFractionUniformName(numLoopIters);
		else if (loopCountType == LOOPCOUNT_DYNAMIC)
			incrementStr = string("ndx += ") + getFloatFractionUniformName(numLoopIters) + "*one";
		else
			DE_ASSERT(false);
	}

	// Loop body.
	string loopBody;

	loopBody = "		res = res.yzwx;\n";

	if (loopType == LOOPTYPE_FOR)
	{
		op << "	for (" + loopCountDeclStr + "; " + loopCmpStr + "; " + incrementStr + ")\n";
		op << "	{\n";
		op << loopBody;
		op << "	}\n";
	}
	else if (loopType == LOOPTYPE_WHILE)
	{
		op << "\t" << loopCountDeclStr + ";\n";
		op << "	while (" + loopCmpStr + ")\n";
		op << "	{\n";
		op << loopBody;
		op << "\t\t" + incrementStr + ";\n";
		op << "	}\n";
	}
	else if (loopType == LOOPTYPE_DO_WHILE)
	{
		op << "\t" << loopCountDeclStr + ";\n";
		op << "	do\n";
		op << "	{\n";
		op << loopBody;
		op << "\t\t" + incrementStr + ";\n";
		op << "	} while (" + loopCmpStr + ");\n";
	}
	else
		DE_ASSERT(false);

	if (isVertexCase)
	{
		vtx << "	v_color = res.rgb;\n";
		frag << "	gl_FragColor = vec4(v_color.rgb, 1.0);\n";
	}
	else
	{
		vtx << "	v_coords = a_coords;\n";
		frag << "	gl_FragColor = vec4(res.rgb, 1.0);\n";

		if (loopCountType == LOOPCOUNT_DYNAMIC)
			vtx << "	v_one = a_one;\n";
	}

	vtx << "}\n";
	frag << "}\n";

	// Fill in shader templates.
	map<string, string> params;
	params.insert(pair<string, string>("LOOP_VAR_TYPE", getDataTypeName(loopCountDataType)));
	params.insert(pair<string, string>("PRECISION", "mediump"));
	params.insert(pair<string, string>("COUNTER_PRECISION", getPrecisionName(loopCountPrecision)));

	StringTemplate vertTemplate(vtx.str().c_str());
	StringTemplate fragTemplate(frag.str().c_str());
	string vertexShaderSource = vertTemplate.specialize(params);
	string fragmentShaderSource = fragTemplate.specialize(params);

	// Create the case.
	ShaderEvalFunc evalFunc = getLoopEvalFunc(numLoopIters);
	LoopRequirement requirement;

	if (loopType == LOOPTYPE_FOR)
	{
		if (loopCountType == LOOPCOUNT_CONSTANT)
			requirement = LOOPREQUIREMENT_STANDARD;
		else if (loopCountType == LOOPCOUNT_UNIFORM)
			requirement = LOOPREQUIREMENT_UNIFORM;
		else
			requirement = LOOPREQUIREMENT_DYNAMIC;
	}
	else
		requirement = LOOPREQUIREMENT_DYNAMIC;

	return new ShaderLoopCase(context, caseName, description, isVertexCase, evalFunc, requirement, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
}

// \todo [petri] Generalize to float as well?
static ShaderLoopCase* createSpecialLoopCase (Context& context, const char* caseName, const char* description, bool isVertexCase, LoopCase loopCase, LoopType loopType, LoopCountType loopCountType)
{
	std::ostringstream vtx;
	std::ostringstream frag;
	std::ostringstream& op = isVertexCase ? vtx : frag;

	vtx << "attribute highp vec4 a_position;\n";
	vtx << "attribute highp vec4 a_coords;\n";

	if (loopCountType == LOOPCOUNT_DYNAMIC)
		vtx << "attribute mediump float a_one;\n";

	// Attribute and varyings.
	if (isVertexCase)
	{
		vtx << "varying mediump vec3 v_color;\n";
		frag << "varying mediump vec3 v_color;\n";
	}
	else
	{
		vtx << "varying mediump vec4 v_coords;\n";
		frag << "varying mediump vec4 v_coords;\n";

		if (loopCountType == LOOPCOUNT_DYNAMIC)
		{
			vtx << "varying mediump float v_one;\n";
			frag << "varying mediump float v_one;\n";
		}
	}

	if (loopCase == LOOPCASE_SELECT_ITERATION_COUNT)
		op << "uniform bool ub_true;\n";

	op << "uniform ${COUNTER_PRECISION} int ui_zero, ui_one, ui_two, ui_three, ui_four, ui_five, ui_six;\n";
	if (loopCase == LOOPCASE_101_ITERATIONS)
		op << "uniform ${COUNTER_PRECISION} int ui_oneHundredOne;\n";

	int iterCount	= 3;	// value to use in loop
	int numIters	= 3;	// actual number of iterations

	// Generate helpers if necessary.
	if (loopCase == LOOPCASE_FUNCTION_CALL_RETURN)
		op << "\n${PRECISION} vec4 func (in ${PRECISION} vec4 coords) { return coords.yzwx; }\n";
	else if (loopCase == LOOPCASE_FUNCTION_CALL_INOUT)
		op << "\nvoid func (inout ${PRECISION} vec4 coords) { coords = coords.yzwx; }\n";

	vtx << "\n";
	vtx << "void main()\n";
	vtx << "{\n";
	vtx << "	gl_Position = a_position;\n";

	frag << "\n";
	frag << "void main()\n";
	frag << "{\n";

	if (loopCountType == LOOPCOUNT_DYNAMIC)
	{
		if (isVertexCase)
			vtx << "	${COUNTER_PRECISION} int one = int(a_one + 0.5);\n";
		else
			frag << "	${COUNTER_PRECISION} int one = int(v_one + 0.5);\n";
	}

	if (isVertexCase)
		vtx << "	${PRECISION} vec4 coords = a_coords;\n";
	else
		frag << "	${PRECISION} vec4 coords = v_coords;\n";

	// Read array.
	op << "	${PRECISION} vec4 res = coords;\n";

	// Handle all loop types.
	string counterPrecisionStr = "mediump";
	string forLoopStr;
	string whileLoopStr;
	string doWhileLoopPreStr;
	string doWhileLoopPostStr;

	if (loopType == LOOPTYPE_FOR)
	{
		switch (loopCase)
		{
			case LOOPCASE_EMPTY_BODY:
				numIters = 0;
				op << "	${FOR_LOOP} {}\n";
				break;

			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST:
				numIters = 0;
				op << "	for (;;) { break; res = res.yzwx; }\n";
				break;

			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST:
				numIters = 1;
				op << "	for (;;) { res = res.yzwx; break; }\n";
				break;

			case LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK:
				numIters = 2;
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	for (;;) { res = res.yzwx; if (i == ${ONE}) break; i++; }\n";
				break;

			case LOOPCASE_SINGLE_STATEMENT:
				op << "	${FOR_LOOP} res = res.yzwx;\n";
				break;

			case LOOPCASE_COMPOUND_STATEMENT:
				iterCount	= 2;
				numIters	= 2 * iterCount;
				op << "	${FOR_LOOP} { res = res.yzwx; res = res.yzwx; }\n";
				break;

			case LOOPCASE_SEQUENCE_STATEMENT:
				iterCount	= 2;
				numIters	= 2 * iterCount;
				op << "	${FOR_LOOP} res = res.yzwx, res = res.yzwx;\n";
				break;

			case LOOPCASE_NO_ITERATIONS:
				iterCount	= 0;
				numIters	= 0;
				op << "	${FOR_LOOP} res = res.yzwx;\n";
				break;

			case LOOPCASE_SINGLE_ITERATION:
				iterCount	= 1;
				numIters	= 1;
				op << "	${FOR_LOOP} res = res.yzwx;\n";
				break;

			case LOOPCASE_SELECT_ITERATION_COUNT:
				op << "	for (int i = 0; i < (ub_true ? ${ITER_COUNT} : 0); i++) res = res.yzwx;\n";
				break;

			case LOOPCASE_CONDITIONAL_CONTINUE:
				numIters = iterCount - 1;
				op << "	${FOR_LOOP} { if (i == ${TWO}) continue; res = res.yzwx; }\n";
				break;

			case LOOPCASE_UNCONDITIONAL_CONTINUE:
				op << "	${FOR_LOOP} { res = res.yzwx; continue; }\n";
				break;

			case LOOPCASE_ONLY_CONTINUE:
				numIters = 0;
				op << "	${FOR_LOOP} { continue; }\n";
				break;

			case LOOPCASE_DOUBLE_CONTINUE:
				numIters = iterCount - 1;
				op << "	${FOR_LOOP} { if (i == ${TWO}) continue; res = res.yzwx; continue; }\n";
				break;

			case LOOPCASE_CONDITIONAL_BREAK:
				numIters = 2;
				op << "	${FOR_LOOP} { if (i == ${TWO}) break; res = res.yzwx; }\n";
				break;

			case LOOPCASE_UNCONDITIONAL_BREAK:
				numIters = 1;
				op << "	${FOR_LOOP} { res = res.yzwx; break; }\n";
				break;

			case LOOPCASE_PRE_INCREMENT:
				op << "	for (int i = 0; i < ${ITER_COUNT}; ++i) { res = res.yzwx; }\n";
				break;

			case LOOPCASE_POST_INCREMENT:
				op << "	${FOR_LOOP} { res = res.yzwx; }\n";
				break;

			case LOOPCASE_MIXED_BREAK_CONTINUE:
				numIters	= 2;
				iterCount	= 5;
				op << "	${FOR_LOOP} { if (i == 0) continue; else if (i == 3) break; res = res.yzwx; }\n";
				break;

			case LOOPCASE_VECTOR_COUNTER:
				op << "	for (${COUNTER_PRECISION} ivec4 i = ivec4(0, 1, ${ITER_COUNT}, 0); i.x < i.z; i.x += i.y) { res = res.yzwx; }\n";
				break;

			case LOOPCASE_101_ITERATIONS:
				numIters = iterCount = 101;
				op << "	${FOR_LOOP} res = res.yzwx;\n";
				break;

			case LOOPCASE_SEQUENCE:
				iterCount	= 5;
				numIters	= 5;
				op << "	${COUNTER_PRECISION} int i;\n";
				op << "	for (i = 0; i < ${TWO}; i++) { res = res.yzwx; }\n";
				op << "	for (; i < ${ITER_COUNT}; i++) { res = res.yzwx; }\n";
				break;

			case LOOPCASE_NESTED:
				numIters = 2 * iterCount;
				op << "	for (${COUNTER_PRECISION} int i = 0; i < ${TWO}; i++)\n";
				op << "	{\n";
				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${ITER_COUNT}; j++)\n";
				op << "			res = res.yzwx;\n";
				op << "	}\n";
				break;

			case LOOPCASE_NESTED_SEQUENCE:
				numIters = 3 * iterCount;
				op << "	for (${COUNTER_PRECISION} int i = 0; i < ${ITER_COUNT}; i++)\n";
				op << "	{\n";
				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${TWO}; j++)\n";
				op << "			res = res.yzwx;\n";
				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${ONE}; j++)\n";
				op << "			res = res.yzwx;\n";
				op << "	}\n";
				break;

			case LOOPCASE_NESTED_TRICKY_DATAFLOW_1:
				numIters = 2;
				op << "	${FOR_LOOP}\n";
				op << "	{\n";
				op << "		res = coords; // ignore outer loop effect \n";
				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${TWO}; j++)\n";
				op << "			res = res.yzwx;\n";
				op << "	}\n";
				break;

			case LOOPCASE_NESTED_TRICKY_DATAFLOW_2:
				numIters = iterCount;
				op << "	${FOR_LOOP}\n";
				op << "	{\n";
				op << "		res = coords.wxyz;\n";
				op << "		for (${COUNTER_PRECISION} int j = 0; j < ${TWO}; j++)\n";
				op << "			res = res.yzwx;\n";
				op << "		coords = res;\n";
				op << "	}\n";
				break;

			case LOOPCASE_CONDITIONAL_BODY:
				numIters = de::min(2, iterCount);
				op << "	${FOR_LOOP} if (i < 2) res = res.yzwx;\n";
				break;

			case LOOPCASE_FUNCTION_CALL_RETURN:
				numIters = iterCount;
				op << "	${FOR_LOOP}\n";
				op << "	{\n";
				op << "		res = func(res);\n";
				op << "	}\n";
				break;

			case LOOPCASE_FUNCTION_CALL_INOUT:
				numIters = iterCount;
				op << "	${FOR_LOOP}\n";
				op << "	{\n";
				op << "		func(res);\n";
				op << "	}\n";
				break;

			default:
				DE_ASSERT(false);
		}

		if (loopCountType == LOOPCOUNT_CONSTANT)
			forLoopStr = string("for (") + counterPrecisionStr + " int i = 0; i < " + de::toString(iterCount) + "; i++)";
		else if (loopCountType == LOOPCOUNT_UNIFORM)
			forLoopStr = string("for (") + counterPrecisionStr + " int i = 0; i < " + getIntUniformName(iterCount) + "; i++)";
		else if (loopCountType == LOOPCOUNT_DYNAMIC)
			forLoopStr = string("for (") + counterPrecisionStr + " int i = 0; i < one*" + getIntUniformName(iterCount) + "; i++)";
		else
			DE_ASSERT(false);
	}
	else if (loopType == LOOPTYPE_WHILE)
	{
		switch (loopCase)
		{
			case LOOPCASE_EMPTY_BODY:
				numIters = 0;
				op << "	${WHILE_LOOP} {}\n";
				break;

			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST:
				numIters = 0;
				op << "	while (true) { break; res = res.yzwx; }\n";
				break;

			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST:
				numIters = 1;
				op << "	while (true) { res = res.yzwx; break; }\n";
				break;

			case LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK:
				numIters = 2;
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	while (true) { res = res.yzwx; if (i == ${ONE}) break; i++; }\n";
				break;

			case LOOPCASE_SINGLE_STATEMENT:
				op << "	${WHILE_LOOP} res = res.yzwx;\n";
				break;

			case LOOPCASE_COMPOUND_STATEMENT:
				iterCount	= 2;
				numIters	= 2 * iterCount;
				op << "	${WHILE_LOOP} { res = res.yzwx; res = res.yzwx; }\n";
				break;

			case LOOPCASE_SEQUENCE_STATEMENT:
				iterCount	= 2;
				numIters	= 2 * iterCount;
				op << "	${WHILE_LOOP} res = res.yzwx, res = res.yzwx;\n";
				break;

			case LOOPCASE_NO_ITERATIONS:
				iterCount	= 0;
				numIters	= 0;
				op << "	${WHILE_LOOP} res = res.yzwx;\n";
				break;

			case LOOPCASE_SINGLE_ITERATION:
				iterCount	= 1;
				numIters	= 1;
				op << "	${WHILE_LOOP} res = res.yzwx;\n";
				break;

			case LOOPCASE_SELECT_ITERATION_COUNT:
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	while (i < (ub_true ? ${ITER_COUNT} : 0)) { res = res.yzwx; i++; }\n";
				break;

			case LOOPCASE_CONDITIONAL_CONTINUE:
				numIters = iterCount - 1;
				op << "	${WHILE_LOOP} { if (i == ${TWO}) continue; res = res.yzwx; }\n";
				break;

			case LOOPCASE_UNCONDITIONAL_CONTINUE:
				op << "	${WHILE_LOOP} { res = res.yzwx; continue; }\n";
				break;

			case LOOPCASE_ONLY_CONTINUE:
				numIters = 0;
				op << "	${WHILE_LOOP} { continue; }\n";
				break;

			case LOOPCASE_DOUBLE_CONTINUE:
				numIters = iterCount - 1;
				op << "	${WHILE_LOOP} { if (i == ${ONE}) continue; res = res.yzwx; continue; }\n";
				break;

			case LOOPCASE_CONDITIONAL_BREAK:
				numIters = 2;
				op << "	${WHILE_LOOP} { if (i == ${THREE}) break; res = res.yzwx; }\n";
				break;

			case LOOPCASE_UNCONDITIONAL_BREAK:
				numIters = 1;
				op << "	${WHILE_LOOP} { res = res.yzwx; break; }\n";
				break;

			case LOOPCASE_PRE_INCREMENT:
				numIters = iterCount - 1;
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	while (++i < ${ITER_COUNT}) { res = res.yzwx; }\n";
				break;

			case LOOPCASE_POST_INCREMENT:
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	while (i++ < ${ITER_COUNT}) { res = res.yzwx; }\n";
				break;

			case LOOPCASE_MIXED_BREAK_CONTINUE:
				numIters	= 2;
				iterCount	= 5;
				op << "	${WHILE_LOOP} { if (i == 0) continue; else if (i == 3) break; res = res.yzwx; }\n";
				break;

			case LOOPCASE_VECTOR_COUNTER:
				op << "	${COUNTER_PRECISION} ivec4 i = ivec4(0, 1, ${ITER_COUNT}, 0);\n";
				op << "	while (i.x < i.z) { res = res.yzwx; i.x += i.y; }\n";
				break;

			case LOOPCASE_101_ITERATIONS:
				numIters = iterCount = 101;
				op << "	${WHILE_LOOP} res = res.yzwx;\n";
				break;

			case LOOPCASE_SEQUENCE:
				iterCount	= 6;
				numIters	= iterCount - 1;
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	while (i++ < ${TWO}) { res = res.yzwx; }\n";
				op << "	while (i++ < ${ITER_COUNT}) { res = res.yzwx; }\n"; // \note skips one iteration
				break;

			case LOOPCASE_NESTED:
				numIters = 2 * iterCount;
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	while (i++ < ${TWO})\n";
				op << "	{\n";
				op << "		${COUNTER_PRECISION} int j = 0;\n";
				op << "		while (j++ < ${ITER_COUNT})\n";
				op << "			res = res.yzwx;\n";
				op << "	}\n";
				break;

			case LOOPCASE_NESTED_SEQUENCE:
				numIters = 2 * iterCount;
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	while (i++ < ${ITER_COUNT})\n";
				op << "	{\n";
				op << "		${COUNTER_PRECISION} int j = 0;\n";
				op << "		while (j++ < ${ONE})\n";
				op << "			res = res.yzwx;\n";
				op << "		while (j++ < ${THREE})\n"; // \note skips one iteration
				op << "			res = res.yzwx;\n";
				op << "	}\n";
				break;

			case LOOPCASE_NESTED_TRICKY_DATAFLOW_1:
				numIters = 2;
				op << "	${WHILE_LOOP}\n";
				op << "	{\n";
				op << "		res = coords; // ignore outer loop effect \n";
				op << "		${COUNTER_PRECISION} int j = 0;\n";
				op << "		while (j++ < ${TWO})\n";
				op << "			res = res.yzwx;\n";
				op << "	}\n";
				break;

			case LOOPCASE_NESTED_TRICKY_DATAFLOW_2:
				numIters = iterCount;
				op << "	${WHILE_LOOP}\n";
				op << "	{\n";
				op << "		res = coords.wxyz;\n";
				op << "		${COUNTER_PRECISION} int j = 0;\n";
				op << "		while (j++ < ${TWO})\n";
				op << "			res = res.yzwx;\n";
				op << "		coords = res;\n";
				op << "	}\n";
				break;

			case LOOPCASE_CONDITIONAL_BODY:
				numIters = de::min(1, iterCount);
				op << "	${WHILE_LOOP} if (i < 2) res = res.yzwx;\n";
				break;

			case LOOPCASE_FUNCTION_CALL_RETURN:
				numIters = iterCount;
				op << "	${WHILE_LOOP}\n";
				op << "	{\n";
				op << "		res = func(res);\n";
				op << "	}\n";
				break;

			case LOOPCASE_FUNCTION_CALL_INOUT:
				numIters = iterCount;
				op << "	${WHILE_LOOP}\n";
				op << "	{\n";
				op << "		func(res);\n";
				op << "	}\n";
				break;

			default:
				DE_ASSERT(false);
		}

		if (loopCountType == LOOPCOUNT_CONSTANT)
			whileLoopStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "	while(i++ < " + de::toString(iterCount) + ")";
		else if (loopCountType == LOOPCOUNT_UNIFORM)
			whileLoopStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "	while(i++ < " + getIntUniformName(iterCount) + ")";
		else if (loopCountType == LOOPCOUNT_DYNAMIC)
			whileLoopStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "	while(i++ < one*" + getIntUniformName(iterCount) + ")";
		else
			DE_ASSERT(false);
	}
	else
	{
		DE_ASSERT(loopType == LOOPTYPE_DO_WHILE);

		switch (loopCase)
		{
			case LOOPCASE_EMPTY_BODY:
				numIters = 0;
				op << "	${DO_WHILE_PRE} {} ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST:
				numIters = 0;
				op << "	do { break; res = res.yzwx; } while (true);\n";
				break;

			case LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST:
				numIters = 1;
				op << "	do { res = res.yzwx; break; } while (true);\n";
				break;

			case LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK:
				numIters = 2;
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	do { res = res.yzwx; if (i == ${ONE}) break; i++; } while (true);\n";
				break;

			case LOOPCASE_SINGLE_STATEMENT:
				op << "	${DO_WHILE_PRE} res = res.yzwx; ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_COMPOUND_STATEMENT:
				iterCount	= 2;
				numIters	= 2 * iterCount;
				op << "	${DO_WHILE_PRE} { res = res.yzwx; res = res.yzwx; } ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_SEQUENCE_STATEMENT:
				iterCount	= 2;
				numIters	= 2 * iterCount;
				op << "	${DO_WHILE_PRE} res = res.yzwx, res = res.yzwx; ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_NO_ITERATIONS:
				DE_ASSERT(false);
				break;

			case LOOPCASE_SINGLE_ITERATION:
				iterCount	= 1;
				numIters	= 1;
				op << "	${DO_WHILE_PRE} res = res.yzwx; ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_SELECT_ITERATION_COUNT:
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	do { res = res.yzwx; } while (++i < (ub_true ? ${ITER_COUNT} : 0));\n";
				break;

			case LOOPCASE_CONDITIONAL_CONTINUE:
				numIters = iterCount - 1;
				op << "	${DO_WHILE_PRE} { if (i == ${TWO}) continue; res = res.yzwx; } ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_UNCONDITIONAL_CONTINUE:
				op << "	${DO_WHILE_PRE} { res = res.yzwx; continue; } ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_ONLY_CONTINUE:
				numIters = 0;
				op << "	${DO_WHILE_PRE} { continue; } ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_DOUBLE_CONTINUE:
				numIters = iterCount - 1;
				op << "	${DO_WHILE_PRE} { if (i == ${TWO}) continue; res = res.yzwx; continue; } ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_CONDITIONAL_BREAK:
				numIters = 2;
				op << "	${DO_WHILE_PRE} { res = res.yzwx; if (i == ${ONE}) break; } ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_UNCONDITIONAL_BREAK:
				numIters = 1;
				op << "	${DO_WHILE_PRE} { res = res.yzwx; break; } ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_PRE_INCREMENT:
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	do { res = res.yzwx; } while (++i < ${ITER_COUNT});\n";
				break;

			case LOOPCASE_POST_INCREMENT:
				numIters = iterCount + 1;
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	do { res = res.yzwx; } while (i++ < ${ITER_COUNT});\n";
				break;

			case LOOPCASE_MIXED_BREAK_CONTINUE:
				numIters	= 2;
				iterCount	= 5;
				op << "	${DO_WHILE_PRE} { if (i == 0) continue; else if (i == 3) break; res = res.yzwx; } ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_VECTOR_COUNTER:
				op << "	${COUNTER_PRECISION} ivec4 i = ivec4(0, 1, ${ITER_COUNT}, 0);\n";
				op << "	do { res = res.yzwx; } while ((i.x += i.y) < i.z);\n";
				break;

			case LOOPCASE_101_ITERATIONS:
				numIters = iterCount = 101;
				op << "	${DO_WHILE_PRE} res = res.yzwx; ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_SEQUENCE:
				iterCount	= 5;
				numIters	= 5;
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	do { res = res.yzwx; } while (++i < ${TWO});\n";
				op << "	do { res = res.yzwx; } while (++i < ${ITER_COUNT});\n";
				break;

			case LOOPCASE_NESTED:
				numIters = 2 * iterCount;
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	do\n";
				op << "	{\n";
				op << "		${COUNTER_PRECISION} int j = 0;\n";
				op << "		do\n";
				op << "			res = res.yzwx;\n";
				op << "		while (++j < ${ITER_COUNT});\n";
				op << "	} while (++i < ${TWO});\n";
				break;

			case LOOPCASE_NESTED_SEQUENCE:
				numIters = 3 * iterCount;
				op << "	${COUNTER_PRECISION} int i = 0;\n";
				op << "	do\n";
				op << "	{\n";
				op << "		${COUNTER_PRECISION} int j = 0;\n";
				op << "		do\n";
				op << "			res = res.yzwx;\n";
				op << "		while (++j < ${TWO});\n";
				op << "		do\n";
				op << "			res = res.yzwx;\n";
				op << "		while (++j < ${THREE});\n";
				op << "	} while (++i < ${ITER_COUNT});\n";
				break;

			case LOOPCASE_NESTED_TRICKY_DATAFLOW_1:
				numIters = 2;
				op << "	${DO_WHILE_PRE}\n";
				op << "	{\n";
				op << "		res = coords; // ignore outer loop effect \n";
				op << "		${COUNTER_PRECISION} int j = 0;\n";
				op << "		do\n";
				op << "			res = res.yzwx;\n";
				op << "		while (++j < ${TWO});\n";
				op << "	} ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_NESTED_TRICKY_DATAFLOW_2:
				numIters = iterCount;
				op << "	${DO_WHILE_PRE}\n";
				op << "	{\n";
				op << "		res = coords.wxyz;\n";
				op << "		${COUNTER_PRECISION} int j = 0;\n";
				op << "		while (j++ < ${TWO})\n";
				op << "			res = res.yzwx;\n";
				op << "		coords = res;\n";
				op << "	} ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_CONDITIONAL_BODY:
				numIters = de::min(2, iterCount);
				op << "	${DO_WHILE_PRE} if (i < 2) res = res.yzwx; ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_FUNCTION_CALL_RETURN:
				numIters = iterCount;
				op << "	${DO_WHILE_PRE}\n";
				op << "	{\n";
				op << "		res = func(res);\n";
				op << "	} ${DO_WHILE_POST}\n";
				break;

			case LOOPCASE_FUNCTION_CALL_INOUT:
				numIters = iterCount;
				op << "	${DO_WHILE_PRE}\n";
				op << "	{\n";
				op << "		func(res);\n";
				op << "	} ${DO_WHILE_POST}\n";
				break;

			default:
				DE_ASSERT(false);
		}

		doWhileLoopPreStr = string("\t") + counterPrecisionStr + " int i = 0;\n" + "\tdo ";
		if (loopCountType == LOOPCOUNT_CONSTANT)
			doWhileLoopPostStr = string(" while (++i < ") + de::toString(iterCount) + ");\n";
		else if (loopCountType == LOOPCOUNT_UNIFORM)
			doWhileLoopPostStr = string(" while (++i < ") + getIntUniformName(iterCount) + ");\n";
		else if (loopCountType == LOOPCOUNT_DYNAMIC)
			doWhileLoopPostStr = string(" while (++i < one*") + getIntUniformName(iterCount) + ");\n";
		else
			DE_ASSERT(false);
	}

	// Shader footers.
	if (isVertexCase)
	{
		vtx << "	v_color = res.rgb;\n";
		frag << "	gl_FragColor = vec4(v_color.rgb, 1.0);\n";
	}
	else
	{
		vtx << "	v_coords = a_coords;\n";
		frag << "	gl_FragColor = vec4(res.rgb, 1.0);\n";

		if (loopCountType == LOOPCOUNT_DYNAMIC)
			vtx << "	v_one = a_one;\n";
	}

	vtx << "}\n";
	frag << "}\n";

	// Constants.
	string oneStr;
	string twoStr;
	string threeStr;
	string iterCountStr;

	if (loopCountType == LOOPCOUNT_CONSTANT)
	{
		oneStr			= "1";
		twoStr			= "2";
		threeStr		= "3";
		iterCountStr	= de::toString(iterCount);
	}
	else if (loopCountType == LOOPCOUNT_UNIFORM)
	{
		oneStr			= "ui_one";
		twoStr			= "ui_two";
		threeStr		= "ui_three";
		iterCountStr	= getIntUniformName(iterCount);
	}
	else if (loopCountType == LOOPCOUNT_DYNAMIC)
	{
		oneStr			= "one*ui_one";
		twoStr			= "one*ui_two";
		threeStr		= "one*ui_three";
		iterCountStr	= string("one*") + getIntUniformName(iterCount);
	}
	else DE_ASSERT(false);

	// Fill in shader templates.
	map<string, string> params;
	params.insert(pair<string, string>("PRECISION", "mediump"));
	params.insert(pair<string, string>("ITER_COUNT", iterCountStr));
	params.insert(pair<string, string>("COUNTER_PRECISION", counterPrecisionStr));
	params.insert(pair<string, string>("FOR_LOOP", forLoopStr));
	params.insert(pair<string, string>("WHILE_LOOP", whileLoopStr));
	params.insert(pair<string, string>("DO_WHILE_PRE", doWhileLoopPreStr));
	params.insert(pair<string, string>("DO_WHILE_POST", doWhileLoopPostStr));
	params.insert(pair<string, string>("ONE", oneStr));
	params.insert(pair<string, string>("TWO", twoStr));
	params.insert(pair<string, string>("THREE", threeStr));

	StringTemplate vertTemplate(vtx.str().c_str());
	StringTemplate fragTemplate(frag.str().c_str());
	string vertexShaderSource = vertTemplate.specialize(params);
	string fragmentShaderSource = fragTemplate.specialize(params);

	// Create the case.
	ShaderEvalFunc evalFunc = getLoopEvalFunc(numIters);
	LoopRequirement requirement;

	if (loopType == LOOPTYPE_FOR && loopCountType == LOOPCOUNT_CONSTANT)
	{
		if (loopCase == LOOPCASE_INFINITE_WITH_CONDITIONAL_BREAK			||
			loopCase == LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_FIRST	||
			loopCase == LOOPCASE_INFINITE_WITH_UNCONDITIONAL_BREAK_LAST		||
			loopCase == LOOPCASE_SELECT_ITERATION_COUNT						||
			loopCase == LOOPCASE_VECTOR_COUNTER								||
			loopCase == LOOPCASE_SEQUENCE)
			requirement = LOOPREQUIREMENT_DYNAMIC;
		else
			requirement = LOOPREQUIREMENT_STANDARD;
	}
	else
		requirement = LOOPREQUIREMENT_DYNAMIC;

	return new ShaderLoopCase(context, caseName, description, isVertexCase, evalFunc, requirement, vertexShaderSource.c_str(), fragmentShaderSource.c_str());
};

// ShaderLoopTests.

ShaderLoopTests::ShaderLoopTests(Context& context)
	: TestCaseGroup(context, "loops", "Loop Tests")
{
}

ShaderLoopTests::~ShaderLoopTests (void)
{
}

void ShaderLoopTests::init (void)
{
	// Loop cases.

	static const ShaderType s_shaderTypes[] =
	{
		SHADERTYPE_VERTEX,
		SHADERTYPE_FRAGMENT
	};

	static const DataType s_countDataType[] =
	{
		TYPE_INT,
		TYPE_FLOAT
	};

	for (int loopType = 0; loopType < LOOPTYPE_LAST; loopType++)
	{
		const char* loopTypeName = getLoopTypeName((LoopType)loopType);

		for (int loopCountType = 0; loopCountType < LOOPCOUNT_LAST; loopCountType++)
		{
			const char* loopCountName = getLoopCountTypeName((LoopCountType)loopCountType);

			string groupName = string(loopTypeName) + "_" + string(loopCountName) + "_iterations";
			string groupDesc = string("Loop tests with ") + loopCountName + " loop counter.";
			TestCaseGroup* group = new TestCaseGroup(m_context, groupName.c_str(), groupDesc.c_str());
			addChild(group);

			// Generic cases.

			for (int precision = 0; precision < PRECISION_LAST; precision++)
			{
				const char* precisionName = getPrecisionName((Precision)precision);

				for (int dataTypeNdx = 0; dataTypeNdx < DE_LENGTH_OF_ARRAY(s_countDataType); dataTypeNdx++)
				{
					DataType loopDataType = s_countDataType[dataTypeNdx];
					const char* dataTypeName = getDataTypeName(loopDataType);

					for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
					{
						ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
						const char*	shaderTypeName	= getShaderTypeName(shaderType);
						bool		isVertexCase	= (shaderType == SHADERTYPE_VERTEX);

						string name = string("basic_") + precisionName + "_" + dataTypeName + "_" + shaderTypeName;
						string desc = string(loopTypeName) + " loop with " + precisionName + dataTypeName + " " + loopCountName + " iteration count in " + shaderTypeName + " shader.";
						group->addChild(createGenericLoopCase(m_context, name.c_str(), desc.c_str(), isVertexCase, (LoopType)loopType, (LoopCountType)loopCountType, (Precision)precision, loopDataType));
					}
				}
			}

			// Special cases.

			for (int loopCase = 0; loopCase < LOOPCASE_LAST; loopCase++)
			{
				const char* loopCaseName = getLoopCaseName((LoopCase)loopCase);

				// no-iterations not possible with do-while.
				if ((loopCase == LOOPCASE_NO_ITERATIONS) && (loopType == LOOPTYPE_DO_WHILE))
					continue;

				for (int shaderTypeNdx = 0; shaderTypeNdx < DE_LENGTH_OF_ARRAY(s_shaderTypes); shaderTypeNdx++)
				{
					ShaderType	shaderType		= s_shaderTypes[shaderTypeNdx];
					const char*	shaderTypeName	= getShaderTypeName(shaderType);
					bool		isVertexCase	= (shaderType == SHADERTYPE_VERTEX);

					string name = string(loopCaseName) + "_" + shaderTypeName;
					string desc = string(loopCaseName) + " loop with " + loopTypeName + " iteration count in " + shaderTypeName + " shader.";
					group->addChild(createSpecialLoopCase(m_context, name.c_str(), desc.c_str(), isVertexCase, (LoopCase)loopCase, (LoopType)loopType, (LoopCountType)loopCountType));
				}
			}
		}
	}

	// Additional smaller handwritten tests.
	const std::vector<tcu::TestNode*> children = gls::ShaderLibrary(m_context.getTestContext(), m_context.getRenderContext(), m_context.getContextInfo()).loadShaderFile("shaders/loops.test");
	for (int i = 0; i < (int)children.size(); i++)
		addChild(children[i]);
}

} // Functional
} // gles2
} // deqp