/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.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 control statement performance tests.
 *//*--------------------------------------------------------------------*/

#include "es3pShaderControlStatementTests.hpp"
#include "glsShaderPerformanceCase.hpp"
#include "tcuTestLog.hpp"

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

#include <string>
#include <vector>

namespace deqp
{
namespace gles3
{
namespace Performance
{

using namespace gls;
using namespace glw; // GL types
using tcu::Vec4;
using tcu::TestLog;
using std::string;
using std::vector;

// Writes the workload expression used in conditional tests.
static void writeConditionalWorkload (std::ostringstream& stream, const char* resultName, const char* operandName)
{
	const int numMultiplications = 64;

	stream << resultName << " = ";

	for (int i = 0; i < numMultiplications; i++)
	{
		if (i > 0)
			stream << "*";

		stream << operandName;
	}

	stream << ";";
}

// Writes the workload expression used in loop tests (one iteration).
static void writeLoopWorkload (std::ostringstream& stream, const char* resultName, const char* operandName)
{
	const int numMultiplications = 8;

	stream << resultName << " = ";

	for (int i = 0; i < numMultiplications; i++)
	{
		if (i > 0)
			stream << " * ";

		stream << "(" << resultName << " + " << operandName << ")";
	}

	stream << ";";
}

// The type of decision to be made in a conditional expression.
// \note In fragment cases with DECISION_ATTRIBUTE, the value in the expression will actually be a varying.
enum DecisionType
{
	DECISION_STATIC = 0,
	DECISION_UNIFORM,
	DECISION_ATTRIBUTE,

	DECISION_LAST
};

class ControlStatementCase :  public ShaderPerformanceCase
{
public:
	ControlStatementCase (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* description, gls::PerfCaseType caseType)
		: ShaderPerformanceCase(testCtx, renderCtx, name, description, caseType)
	{
	}

	void init (void)
	{
		m_testCtx.getLog() << TestLog::Message << "Using additive blending." << TestLog::EndMessage;
		ShaderPerformanceCase::init();
	}

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

		gl.enable(GL_BLEND);
		gl.blendEquation(GL_FUNC_ADD);
		gl.blendFunc(GL_ONE, GL_ONE);
	}
};

class ConditionalCase : public ControlStatementCase
{
public:
	enum BranchResult
	{
		BRANCH_TRUE = 0,
		BRANCH_FALSE,
		BRANCH_MIXED,

		BRANCH_LAST
	};

	enum WorkloadDivision
	{
		WORKLOAD_DIVISION_EVEN = 0,		//! Both true and false branches contain same amount of computation.
		WORKLOAD_DIVISION_TRUE_HEAVY,	//! True branch contains more computation.
		WORKLOAD_DIVISION_FALSE_HEAVY,	//! False branch contains more computation.

		WORKLOAD_DIVISION_LAST
	};

						ConditionalCase		(Context& context, const char* name, const char* description, DecisionType decisionType, BranchResult branchType, WorkloadDivision workloadDivision, bool isVertex);
						~ConditionalCase	(void);

	void				init				(void);
	void				deinit				(void);
	void				setupProgram		(deUint32 program);

private:
	DecisionType		m_decisionType;
	BranchResult		m_branchType;
	WorkloadDivision	m_workloadDivision;

	vector<float>		m_comparisonValueArray; // Will contain per-vertex comparison values if using mixed branch type in vertex case.
	deUint32			m_arrayBuffer;
};

ConditionalCase::ConditionalCase (Context& context, const char* name, const char* description, DecisionType decisionType, BranchResult branchType, WorkloadDivision workloadDivision, bool isVertex)
	: ControlStatementCase			(context.getTestContext(), context.getRenderContext(), name, description, isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT)
	, m_decisionType				(decisionType)
	, m_branchType					(branchType)
	, m_workloadDivision			(workloadDivision)
	, m_arrayBuffer					(0)
{
}

void ConditionalCase::init (void)
{
	bool			isVertexCase		= m_caseType == CASETYPE_VERTEX;

	bool			isStaticCase		= m_decisionType == DECISION_STATIC;
	bool			isUniformCase		= m_decisionType == DECISION_UNIFORM;
	bool			isAttributeCase		= m_decisionType == DECISION_ATTRIBUTE;

	DE_ASSERT(isStaticCase || isUniformCase || isAttributeCase);

	bool			isConditionTrue		= m_branchType == BRANCH_TRUE;
	bool			isConditionFalse	= m_branchType == BRANCH_FALSE;
	bool			isConditionMixed	= m_branchType == BRANCH_MIXED;

	DE_ASSERT(isConditionTrue || isConditionFalse || isConditionMixed);
	DE_UNREF(isConditionFalse);

	DE_ASSERT(isAttributeCase || !isConditionMixed); // The branch taken can vary between executions only if using attribute input.

	const char*		staticCompareValueStr	= isConditionTrue	? "1.0" : "-1.0";
	const char*		compareValueStr			= isStaticCase		? staticCompareValueStr :
											  isUniformCase		? "u_compareValue" :
											  isVertexCase		? "a_compareValue" :
																  "v_compareValue";

	std::ostringstream	vtx;
	std::ostringstream	frag;
	std::ostringstream&	op		= isVertexCase ? vtx : frag;

	vtx << "#version 300 es\n";
	vtx << "in highp vec4 a_position;\n";	// Position attribute.
	vtx << "in mediump vec4 a_value0;\n";	// Input for workload calculations of "true" branch.
	vtx << "in mediump vec4 a_value1;\n";	// Input for workload calculations of "false" branch.

	frag << "#version 300 es\n";
	frag << "layout(location = 0) out mediump vec4 o_color;\n";

	// Value to be used in the conditional expression.
	if (isAttributeCase)
		vtx << "in mediump float a_compareValue;\n";
	else if (isUniformCase)
		op << "uniform mediump float u_compareValue;\n";

	// Varyings.
	if (isVertexCase)
	{
		vtx << "out mediump vec4 v_color;\n";
		frag << "in mediump vec4 v_color;\n";
	}
	else
	{
		vtx << "out mediump vec4 v_value0;\n";
		vtx << "out mediump vec4 v_value1;\n";
		frag << "in mediump vec4 v_value0;\n";
		frag << "in mediump vec4 v_value1;\n";

		if (isAttributeCase)
		{
			vtx << "out mediump float v_compareValue;\n";
			frag << "in mediump float v_compareValue;\n";
		}
	}

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

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

	op << "	mediump vec4 res;\n";

	string condition;

	if (isConditionMixed && !isVertexCase)
		condition = string("") + "fract(" + compareValueStr + ") < 0.5"; // Comparison result varies with high frequency.
	else
		condition = string("") + compareValueStr + " > 0.0";

	op << "	if (" << condition << ")\n";
	op << "	{\n";

	op << "\t\t";
	if (m_workloadDivision == WORKLOAD_DIVISION_EVEN || m_workloadDivision == WORKLOAD_DIVISION_TRUE_HEAVY)
		writeConditionalWorkload(op, "res", isVertexCase ? "a_value0" : "v_value0"); // Workload calculation for the "true" branch.
	else
		op << "res = " << (isVertexCase ? "a_value0" : "v_value0") << ";";
	op << "\n";

	op << "	}\n";
	op << "	else\n";
	op << "	{\n";

	op << "\t\t";
	if (m_workloadDivision == WORKLOAD_DIVISION_EVEN || m_workloadDivision == WORKLOAD_DIVISION_FALSE_HEAVY)
		writeConditionalWorkload(op, "res", isVertexCase ? "a_value1" : "v_value1"); // Workload calculations for the "false" branch.
	else
		op << "res = " << (isVertexCase ? "a_value1" : "v_value1") << ";";
	op << "\n";

	op << "	}\n";

	if (isVertexCase)
	{
		// Put result to color variable.
		vtx << "	v_color = res;\n";
		frag << "	o_color = v_color;\n";
	}
	else
	{
		// Transfer inputs to fragment shader through varyings.
		if (isAttributeCase)
			vtx << "	v_compareValue = a_compareValue;\n";
		vtx << "	v_value0 = a_value0;\n";
		vtx << "	v_value1 = a_value1;\n";

		frag << "	o_color = res;\n"; // Put result to color variable.
	}

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

	m_vertShaderSource = vtx.str();
	m_fragShaderSource = frag.str();

	if (isAttributeCase)
	{
		if (!isConditionMixed)
		{
			// Every execution takes the same branch.

			float value = isConditionTrue ? +1.0f : -1.0f;
			m_attributes.push_back(AttribSpec("a_compareValue",	Vec4(value, 0.0f, 0.0f, 0.0f),
																Vec4(value, 0.0f, 0.0f, 0.0f),
																Vec4(value, 0.0f, 0.0f, 0.0f),
																Vec4(value, 0.0f, 0.0f, 0.0f)));
		}
		else if (isVertexCase)
		{
			// Vertex case, not every execution takes the same branch.

			const int	numComponents	= 4;
			int			numVertices		= (getGridWidth() + 1) * (getGridHeight() + 1);

			// setupProgram() will later bind this array as an attribute.
			m_comparisonValueArray.resize(numVertices * numComponents);

			// Make every second vertex take the true branch, and every second the false branch.
			for (int i = 0; i < (int)m_comparisonValueArray.size(); i++)
			{
				if (i % numComponents == 0)
					m_comparisonValueArray[i] = (i / numComponents) % 2 == 0 ? +1.0f : -1.0f;
				else
					m_comparisonValueArray[i] = 0.0f;
			}
		}
		else // isConditionMixed && !isVertexCase
		{
			// Fragment case, not every execution takes the same branch.
			// \note fract(a_compareValue) < 0.5 will be true for every second column of fragments.

			float minValue = 0.0f;
			float maxValue = (float)getViewportWidth()*0.5f;
			m_attributes.push_back(AttribSpec("a_compareValue",	Vec4(minValue, 0.0f, 0.0f, 0.0f),
																Vec4(maxValue, 0.0f, 0.0f, 0.0f),
																Vec4(minValue, 0.0f, 0.0f, 0.0f),
																Vec4(maxValue, 0.0f, 0.0f, 0.0f)));
		}
	}

	m_attributes.push_back(AttribSpec("a_value0",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));

	m_attributes.push_back(AttribSpec("a_value1",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));

	ControlStatementCase::init();
}

void ConditionalCase::setupProgram (deUint32 program)
{
	const glw::Functions& gl = m_renderCtx.getFunctions();

	if (m_decisionType == DECISION_UNIFORM)
	{
		int location = gl.getUniformLocation(program, "u_compareValue");
		gl.uniform1f(location, m_branchType == BRANCH_TRUE ? +1.0f : -1.0f);
	}
	else if (m_decisionType == DECISION_ATTRIBUTE && m_branchType == BRANCH_MIXED && m_caseType == CASETYPE_VERTEX)
	{
		// Setup per-vertex comparison values calculated in init().

		const int	numComponents			= 4;
		int			compareAttribLocation	= gl.getAttribLocation(program, "a_compareValue");

		DE_ASSERT((int)m_comparisonValueArray.size() == numComponents * (getGridWidth() + 1) * (getGridHeight() + 1));

		gl.genBuffers(1, &m_arrayBuffer);
		gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuffer);
		gl.bufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(m_comparisonValueArray.size()*sizeof(float)), &m_comparisonValueArray[0], GL_STATIC_DRAW);
		gl.enableVertexAttribArray(compareAttribLocation);
		gl.vertexAttribPointer(compareAttribLocation, (GLint)numComponents, GL_FLOAT, GL_FALSE, 0, DE_NULL);
	}

	GLU_EXPECT_NO_ERROR(gl.getError(), "Setup program state");
}

ConditionalCase::~ConditionalCase (void)
{
	const glw::Functions& gl = m_renderCtx.getFunctions();

	if (m_arrayBuffer != 0)
	{
		gl.deleteBuffers(1, &m_arrayBuffer);
		m_arrayBuffer = 0;
	}
}

void ConditionalCase::deinit (void)
{
	const glw::Functions& gl = m_renderCtx.getFunctions();

	m_comparisonValueArray.clear();

	if (m_arrayBuffer != 0)
	{
		gl.deleteBuffers(1, &m_arrayBuffer);
		m_arrayBuffer = 0;
	}

	ShaderPerformanceCase::deinit();
}

class LoopCase : public ControlStatementCase
{
public:
	enum LoopType
	{
		LOOP_FOR = 0,
		LOOP_WHILE,
		LOOP_DO_WHILE,

		LOOP_LAST
	};
					LoopCase			(Context& context, const char* name, const char* description, LoopType type, DecisionType decisionType, bool isLoopBoundStable, bool isVertex);
					~LoopCase			(void);

	void			init				(void);
	void			deinit				(void);
	void			setupProgram		(deUint32 program);

private:
	DecisionType	m_decisionType;
	LoopType		m_type;

	bool			m_isLoopBoundStable;	// Whether loop bound is same in all executions.
	vector<float>	m_boundArray;			// Will contain per-vertex loop bounds if using non-stable attribute in vertex case.
	deUint32		m_arrayBuffer;
};

LoopCase::LoopCase (Context& context, const char* name, const char* description, LoopType type, DecisionType decisionType, bool isLoopBoundStable, bool isVertex)
	: ControlStatementCase	(context.getTestContext(), context.getRenderContext(), name, description, isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT)
	, m_decisionType		(decisionType)
	, m_type				(type)
	, m_isLoopBoundStable	(isLoopBoundStable)
	, m_arrayBuffer			(0)
{
}

void LoopCase::init (void)
{
	bool				isVertexCase	= m_caseType == CASETYPE_VERTEX;

	bool				isStaticCase	= m_decisionType == DECISION_STATIC;
	bool				isUniformCase	= m_decisionType == DECISION_UNIFORM;
	bool				isAttributeCase	= m_decisionType == DECISION_ATTRIBUTE;

	DE_ASSERT(isStaticCase || isUniformCase || isAttributeCase);

	DE_ASSERT(m_type == LOOP_FOR		||
			  m_type == LOOP_WHILE		||
			  m_type == LOOP_DO_WHILE);

	DE_ASSERT(isAttributeCase || m_isLoopBoundStable); // The loop bound count can vary between executions only if using attribute input.

	// \note The fractional part is .5 (instead of .0) so that these can be safely used as loop bounds.
	const float			loopBound				= 10.5f;
	const float			unstableBoundLow		= 5.5f;
	const float			unstableBoundHigh		= 15.5f;
	static const char*	loopBoundStr			= "10.5";
	static const char*	unstableBoundLowStr		= "5.5";
	static const char*	unstableBoundHighStr	= "15.5";

	const char*			boundValueStr		= isStaticCase			? loopBoundStr :
											  isUniformCase			? "u_bound" :
											  isVertexCase			? "a_bound" :
											  m_isLoopBoundStable	? "v_bound" :
																	  "loopBound";

	std::ostringstream	vtx;
	std::ostringstream	frag;
	std::ostringstream&	op		= isVertexCase ? vtx : frag;

	vtx << "#version 300 es\n";
	vtx << "in highp vec4 a_position;\n";	// Position attribute.
	vtx << "in mediump vec4 a_value;\n";	// Input for workload calculations.

	frag << "#version 300 es\n";
	frag << "layout(location = 0) out mediump vec4 o_color;\n";

	// Value to be used as the loop iteration count.
	if (isAttributeCase)
		vtx << "in mediump float a_bound;\n";
	else if (isUniformCase)
		op << "uniform mediump float u_bound;\n";

	// Varyings.
	if (isVertexCase)
	{
		vtx << "out mediump vec4 v_color;\n";
		frag << "in mediump vec4 v_color;\n";
	}
	else
	{
		vtx << "out mediump vec4 v_value;\n";
		frag << "in mediump vec4 v_value;\n";

		if (isAttributeCase)
		{
			vtx << "out mediump float v_bound;\n";
			frag << "in mediump float v_bound;\n";
		}
	}

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

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

	op << "	mediump vec4 res = vec4(0.0);\n";

	if (!m_isLoopBoundStable && !isVertexCase)
	{
		// Choose the actual loop bound based on v_bound.
		// \note Loop bound will vary with high frequency between fragment columns, given appropriate range for v_bound.
		op << "	mediump float loopBound = fract(v_bound) < 0.5 ? " << unstableBoundLowStr << " : " << unstableBoundHighStr << ";\n";
	}

	// Start a for, while or do-while loop.
	if (m_type == LOOP_FOR)
		op << "	for (mediump float i = 0.0; i < " << boundValueStr << "; i++)\n";
	else
	{
		op << "	mediump float i = 0.0;\n";
		if (m_type == LOOP_WHILE)
			op << "	while (i < " << boundValueStr << ")\n";
		else // LOOP_DO_WHILE
			op << "	do\n";
	}
	op << "	{\n";

	// Workload calculations inside the loop.
	op << "\t\t";
	writeLoopWorkload(op, "res", isVertexCase ? "a_value" : "v_value");
	op << "\n";

	// Only "for" has counter increment in the loop head.
	if (m_type != LOOP_FOR)
		op << "		i++;\n";

	// End the loop.
	if (m_type == LOOP_DO_WHILE)
		op << "	} while (i < " << boundValueStr << ");\n";
	else
		op << "	}\n";

	if (isVertexCase)
	{
		// Put result to color variable.
		vtx << "	v_color = res;\n";
		frag << "	o_color = v_color;\n";
	}
	else
	{
		// Transfer inputs to fragment shader through varyings.
		if (isAttributeCase)
			vtx << "	v_bound = a_bound;\n";
		vtx << "	v_value = a_value;\n";

		frag << "	o_color = res;\n"; // Put result to color variable.
	}

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

	m_vertShaderSource = vtx.str();
	m_fragShaderSource = frag.str();

	if (isAttributeCase)
	{
		if (m_isLoopBoundStable)
		{
			// Every execution has same number of iterations.

			m_attributes.push_back(AttribSpec("a_bound",	Vec4(loopBound, 0.0f, 0.0f, 0.0f),
															Vec4(loopBound, 0.0f, 0.0f, 0.0f),
															Vec4(loopBound, 0.0f, 0.0f, 0.0f),
															Vec4(loopBound, 0.0f, 0.0f, 0.0f)));
		}
		else if (isVertexCase)
		{
			// Vertex case, with non-constant number of iterations.

			const int	numComponents	= 4;
			int			numVertices		= (getGridWidth() + 1) * (getGridHeight() + 1);

			// setupProgram() will later bind this array as an attribute.
			m_boundArray.resize(numVertices * numComponents);

			// Vary between low and high loop bounds; they should average to loopBound however.
			for (int i = 0; i < (int)m_boundArray.size(); i++)
			{
				if (i % numComponents == 0)
					m_boundArray[i] = (i / numComponents) % 2 == 0 ? unstableBoundLow : unstableBoundHigh;
				else
					m_boundArray[i] = 0.0f;
			}
		}
		else // !m_isLoopBoundStable && !isVertexCase
		{
			// Fragment case, with non-constant number of iterations.
			// \note fract(a_bound) < 0.5 will be true for every second fragment.

			float minValue = 0.0f;
			float maxValue = (float)getViewportWidth()*0.5f;
			m_attributes.push_back(AttribSpec("a_bound",	Vec4(minValue, 0.0f, 0.0f, 0.0f),
															Vec4(maxValue, 0.0f, 0.0f, 0.0f),
															Vec4(minValue, 0.0f, 0.0f, 0.0f),
															Vec4(maxValue, 0.0f, 0.0f, 0.0f)));
		}
	}

	m_attributes.push_back(AttribSpec("a_value",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));

	ControlStatementCase::init();
}

void LoopCase::setupProgram (deUint32 program)
{
	const glw::Functions& gl = m_renderCtx.getFunctions();

	if (m_decisionType == DECISION_UNIFORM)
	{
		const float loopBound = 10.5f;

		int location = gl.getUniformLocation(program, "u_bound");
		gl.uniform1f(location, loopBound);
	}
	else if (m_decisionType == DECISION_ATTRIBUTE && !m_isLoopBoundStable && m_caseType == CASETYPE_VERTEX)
	{
		// Setup per-vertex loop bounds calculated in init().

		const int	numComponents		= 4;
		int			boundAttribLocation	= gl.getAttribLocation(program, "a_bound");

		DE_ASSERT((int)m_boundArray.size() == numComponents * (getGridWidth() + 1) * (getGridHeight() + 1));

		gl.genBuffers(1, &m_arrayBuffer);
		gl.bindBuffer(GL_ARRAY_BUFFER, m_arrayBuffer);
		gl.bufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(m_boundArray.size()*sizeof(float)), &m_boundArray[0], GL_STATIC_DRAW);
		gl.enableVertexAttribArray(boundAttribLocation);
		gl.vertexAttribPointer(boundAttribLocation, (GLint)numComponents, GL_FLOAT, GL_FALSE, 0, DE_NULL);
	}

	GLU_EXPECT_NO_ERROR(gl.getError(), "Setup program state");
}

LoopCase::~LoopCase (void)
{
	const glw::Functions& gl = m_renderCtx.getFunctions();

	if (m_arrayBuffer)
	{
		gl.deleteBuffers(1, &m_arrayBuffer);
		m_arrayBuffer = 0;
	}
}

void LoopCase::deinit (void)
{
	const glw::Functions& gl = m_renderCtx.getFunctions();

	m_boundArray.clear();

	if (m_arrayBuffer)
	{
		gl.deleteBuffers(1, &m_arrayBuffer);
		m_arrayBuffer = 0;
	}

	ShaderPerformanceCase::deinit();
}

// A reference case, same calculations as the actual tests but without control statements.
class WorkloadReferenceCase : public ControlStatementCase
{
public:
							WorkloadReferenceCase		(Context& context, const char* name, const char* description, bool isVertex);

	void					init						(void);

protected:
	virtual void			writeWorkload				(std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const = 0;
};

WorkloadReferenceCase::WorkloadReferenceCase (Context& context, const char* name, const char* description, bool isVertex)
	: ControlStatementCase(context.getTestContext(), context.getRenderContext(), name, description, isVertex ? CASETYPE_VERTEX : CASETYPE_FRAGMENT)
{
}

void WorkloadReferenceCase::init (void)
{
	bool isVertexCase = m_caseType == CASETYPE_VERTEX;

	std::ostringstream	vtx;
	std::ostringstream	frag;
	std::ostringstream&	op			= isVertexCase ? vtx : frag;

	vtx << "#version 300 es\n";
	vtx << "in highp vec4 a_position;\n";	// Position attribute.
	vtx << "in mediump vec4 a_value;\n";	// Value for workload calculations.

	frag << "#version 300 es\n";
	frag << "layout(location = 0) out mediump vec4 o_color;\n";

	// Varyings.
	if (isVertexCase)
	{
		vtx << "out mediump vec4 v_color;\n";
		frag << "in mediump vec4 v_color;\n";
	}
	else
	{
		vtx << "out mediump vec4 v_value;\n";
		frag << "in mediump vec4 v_value;\n";
	}

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

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

	op << "\tmediump vec4 res;\n";
	writeWorkload(op, "res", isVertexCase ? "a_value" : "v_value");

	if (isVertexCase)
	{
		// Put result to color variable.
		vtx << "	v_color = res;\n";
		frag << "	o_color = v_color;\n";
	}
	else
	{
		vtx << "	v_value = a_value;\n";	// Transfer input to fragment shader through varying.
		frag << "	o_color = res;\n";	// Put result to color variable.
	}

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

	m_vertShaderSource = vtx.str();
	m_fragShaderSource = frag.str();

	m_attributes.push_back(AttribSpec("a_value",	Vec4(0.0f, 0.1f, 0.2f, 0.3f),
													Vec4(0.4f, 0.5f, 0.6f, 0.7f),
													Vec4(0.8f, 0.9f, 1.0f, 1.1f),
													Vec4(1.2f, 1.3f, 1.4f, 1.5f)));

	ControlStatementCase::init();
}

class LoopWorkloadReferenceCase : public WorkloadReferenceCase
{
public:
	LoopWorkloadReferenceCase (Context& context, const char* name, const char* description, bool isAttributeStable, bool isVertex)
		: WorkloadReferenceCase		(context, name, description, isVertex)
		, m_isAttributeStable		(isAttributeStable)
	{
	}

protected:
	void writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const;

private:
	bool m_isAttributeStable;
};

void LoopWorkloadReferenceCase::writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const
{
	const int	loopIterations	= 11;
	bool		isVertexCase	= m_caseType == CASETYPE_VERTEX;

	dst << "\t" << resultVariableName << " = vec4(0.0);\n";

	for (int i = 0; i < loopIterations; i++)
	{
		dst << "\t";
		writeLoopWorkload(dst, resultVariableName, inputVariableName);
		dst << "\n";
	}

	if (!isVertexCase && !m_isAttributeStable)
	{
		// Corresponds to the fract() done in a real test's fragment case with non-stable attribute.
		dst << "	res.x = fract(res.x);\n";
	}
}

class ConditionalWorkloadReferenceCase : public WorkloadReferenceCase
{
public:
	ConditionalWorkloadReferenceCase (Context& context, const char* name, const char* description, bool isAttributeStable, bool isVertex)
		: WorkloadReferenceCase		(context, name, description, isVertex)
		, m_isAttributeStable		(isAttributeStable)
	{
	}

protected:
	void writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const;

private:
	bool m_isAttributeStable;
};

void ConditionalWorkloadReferenceCase::writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const
{
	bool isVertexCase = m_caseType == CASETYPE_VERTEX;

	dst << "\t";
	writeConditionalWorkload(dst, resultVariableName, inputVariableName);
	dst << "\n";

	if (!isVertexCase && !m_isAttributeStable)
	{
		// Corresponds to the fract() done in a real test's fragment case with non-stable attribute.
		dst << "	res.x = fract(res.x);\n";
	}
}

// A workload reference case for e.g. a conditional case with a branch with no computation.
class EmptyWorkloadReferenceCase : public WorkloadReferenceCase
{
public:
	EmptyWorkloadReferenceCase	(Context& context, const char* name, const char* description, bool isVertex)
		: WorkloadReferenceCase (context, name, description, isVertex)
	{
	}

protected:
	void writeWorkload (std::ostringstream& dst, const char* resultVariableName, const char* inputVariableName) const
	{
		dst << "\t" << resultVariableName << " = " << inputVariableName << ";\n";
	}
};

ShaderControlStatementTests::ShaderControlStatementTests (Context& context)
	: TestCaseGroup(context, "control_statement", "Control Statement Performance Tests")
{
}

ShaderControlStatementTests::~ShaderControlStatementTests (void)
{
}

void ShaderControlStatementTests::init (void)
{
	// Conditional cases (if-else).

	tcu::TestCaseGroup* ifElseGroup = new tcu::TestCaseGroup(m_testCtx, "if_else", "if-else Conditional Performance Tests");
	addChild(ifElseGroup);

	for (int isFrag = 0; isFrag <= 1; isFrag++)
	{
		bool isVertex = isFrag == 0;
		ShaderPerformanceCaseGroup* vertexOrFragmentGroup = new ShaderPerformanceCaseGroup(m_testCtx, isVertex ? "vertex" : "fragment", "");
		ifElseGroup->addChild(vertexOrFragmentGroup);

		DE_STATIC_ASSERT(DECISION_STATIC == 0);
		for (int decisionType = (int)DECISION_STATIC; decisionType < (int)DECISION_LAST; decisionType++)
		{
			const char* decisionName = decisionType == (int)DECISION_STATIC		? "static" :
										decisionType == (int)DECISION_UNIFORM	? "uniform" :
										decisionType == (int)DECISION_ATTRIBUTE	? (isVertex ? "attribute" : "varying") :
																					DE_NULL;
			DE_ASSERT(decisionName != DE_NULL);

			for (int workloadDivision = 0; workloadDivision < ConditionalCase::WORKLOAD_DIVISION_LAST; workloadDivision++)
			{
				const char* workloadDivisionSuffix = workloadDivision == (int)ConditionalCase::WORKLOAD_DIVISION_EVEN			? "" :
													 workloadDivision == (int)ConditionalCase::WORKLOAD_DIVISION_TRUE_HEAVY		? "_with_heavier_true" :
													 workloadDivision == (int)ConditionalCase::WORKLOAD_DIVISION_FALSE_HEAVY	? "_with_heavier_false" :
																																  DE_NULL;
				DE_ASSERT(workloadDivisionSuffix != DE_NULL);

				DE_STATIC_ASSERT(ConditionalCase::BRANCH_TRUE == 0);
				for (int branchResult = (int)ConditionalCase::BRANCH_TRUE; branchResult < (int)ConditionalCase::BRANCH_LAST; branchResult++)
				{
					if (decisionType != (int)DECISION_ATTRIBUTE && branchResult == (int)ConditionalCase::BRANCH_MIXED)
						continue;

					const char* branchResultName = branchResult == (int)ConditionalCase::BRANCH_TRUE	? "true" :
												   branchResult == (int)ConditionalCase::BRANCH_FALSE	? "false" :
												   branchResult == (int)ConditionalCase::BRANCH_MIXED	? "mixed" :
																										  DE_NULL;
					DE_ASSERT(branchResultName != DE_NULL);

					string caseName = string("") + decisionName + "_" + branchResultName + workloadDivisionSuffix;

					vertexOrFragmentGroup->addChild(new ConditionalCase(m_context, caseName.c_str(), "",
																		(DecisionType)decisionType, (ConditionalCase::BranchResult)branchResult,
																		(ConditionalCase::WorkloadDivision)workloadDivision, isVertex));
				}
			}
		}

		if (isVertex)
			vertexOrFragmentGroup->addChild(new ConditionalWorkloadReferenceCase(m_context, "reference", "", true, isVertex));
		else
		{
			// Only fragment case with BRANCH_MIXED has an additional fract() call.
			vertexOrFragmentGroup->addChild(new ConditionalWorkloadReferenceCase(m_context, "reference_unmixed", "", true, isVertex));
			vertexOrFragmentGroup->addChild(new ConditionalWorkloadReferenceCase(m_context, "reference_mixed", "", false, isVertex));
		}

		vertexOrFragmentGroup->addChild(new EmptyWorkloadReferenceCase(m_context, "reference_empty", "", isVertex));
	}

	// Loop cases.

	static const struct
	{
		LoopCase::LoopType	type;
		const char*			name;
		const char*			description;
	} loopGroups[] =
	{
		{LoopCase::LOOP_FOR,		"for",		"for Loop Performance Tests"},
		{LoopCase::LOOP_WHILE,		"while",	"while Loop Performance Tests"},
		{LoopCase::LOOP_DO_WHILE,	"do_while",	"do-while Loop Performance Tests"}
	};

	for (int groupNdx = 0; groupNdx < DE_LENGTH_OF_ARRAY(loopGroups); groupNdx++)
	{
		tcu::TestCaseGroup* currentLoopGroup = new tcu::TestCaseGroup(m_testCtx, loopGroups[groupNdx].name, loopGroups[groupNdx].description);
		addChild(currentLoopGroup);

		for (int isFrag = 0; isFrag <= 1; isFrag++)
		{
			bool isVertex = isFrag == 0;
			ShaderPerformanceCaseGroup* vertexOrFragmentGroup = new ShaderPerformanceCaseGroup(m_testCtx, isVertex ? "vertex" : "fragment", "");
			currentLoopGroup->addChild(vertexOrFragmentGroup);

			DE_STATIC_ASSERT(DECISION_STATIC == 0);
			for (int decisionType = (int)DECISION_STATIC; decisionType < (int)DECISION_LAST; decisionType++)
			{
				const char* decisionName = decisionType == (int)DECISION_STATIC		? "static" :
										   decisionType == (int)DECISION_UNIFORM	? "uniform" :
										   decisionType == (int)DECISION_ATTRIBUTE	? (isVertex ? "attribute" : "varying") :
																					  DE_NULL;
				DE_ASSERT(decisionName != DE_NULL);

				if (decisionType == (int)DECISION_ATTRIBUTE)
				{
					vertexOrFragmentGroup->addChild(new LoopCase(m_context, (string(decisionName) + "_stable").c_str(), "", loopGroups[groupNdx].type, (DecisionType)decisionType, true, isVertex));
					vertexOrFragmentGroup->addChild(new LoopCase(m_context, (string(decisionName) + "_unstable").c_str(), "", loopGroups[groupNdx].type, (DecisionType)decisionType, false, isVertex));
				}
				else
					vertexOrFragmentGroup->addChild(new LoopCase(m_context, decisionName, "", loopGroups[groupNdx].type, (DecisionType)decisionType, true, isVertex));

			}

			if (isVertex)
				vertexOrFragmentGroup->addChild(new LoopWorkloadReferenceCase(m_context, "reference", "", true, isVertex));
			else
			{
				// Only fragment case with unstable attribute has an additional fract() call.
				vertexOrFragmentGroup->addChild(new LoopWorkloadReferenceCase(m_context, "reference_stable", "", true, isVertex));
				vertexOrFragmentGroup->addChild(new LoopWorkloadReferenceCase(m_context, "reference_unstable", "", false, isVertex));
			}
		}
	}
}

} // Performance
} // gles3
} // deqp