/*-------------------------------------------------------------------------
 * 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 Tests for separate shader objects
 *//*--------------------------------------------------------------------*/

#include "es31fSeparateShaderTests.hpp"

#include "deInt32.h"
#include "deString.h"
#include "deStringUtil.hpp"
#include "deUniquePtr.hpp"
#include "deRandom.hpp"
#include "deSTLUtil.hpp"
#include "tcuCommandLine.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuResultCollector.hpp"
#include "tcuRGBA.hpp"
#include "tcuSurface.hpp"
#include "tcuStringTemplate.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluPixelTransfer.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluVarType.hpp"
#include "glsShaderLibrary.hpp"
#include "glwFunctions.hpp"
#include "glwDefs.hpp"
#include "glwEnums.hpp"

#include <cstdarg>
#include <algorithm>
#include <map>
#include <sstream>
#include <string>
#include <set>
#include <vector>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using std::map;
using std::set;
using std::ostringstream;
using std::string;
using std::vector;
using de::MovePtr;
using de::Random;
using de::UniquePtr;
using tcu::MessageBuilder;
using tcu::RenderTarget;
using tcu::StringTemplate;
using tcu::Surface;
using tcu::TestLog;
using tcu::ResultCollector;
using glu::CallLogWrapper;
using glu::DataType;
using glu::VariableDeclaration;
using glu::Precision;
using glu::Program;
using glu::ProgramPipeline;
using glu::ProgramSources;
using glu::RenderContext;
using glu::ShaderProgram;
using glu::ShaderType;
using glu::Storage;
using glu::VarType;
using glu::VertexSource;
using glu::FragmentSource;
using glu::ProgramSeparable;

using namespace glw;

#define LOG_CALL(CALL) do	\
{							\
	enableLogging(true);	\
	CALL;					\
	enableLogging(false);	\
} while (deGetFalse())

enum
{
	VIEWPORT_SIZE = 128
};

enum VaryingInterpolation
{
	VARYINGINTERPOLATION_SMOOTH		= 0,
	VARYINGINTERPOLATION_FLAT,
	VARYINGINTERPOLATION_CENTROID,
	VARYINGINTERPOLATION_DEFAULT,
	VARYINGINTERPOLATION_RANDOM,

	VARYINGINTERPOLATION_LAST
};

DataType randomType (Random& rnd)
{
	using namespace glu;

	if (rnd.getInt(0, 7) == 0)
	{
		const int numCols = rnd.getInt(2, 4), numRows = rnd.getInt(2, 4);

		return getDataTypeMatrix(numCols, numRows);
	}
	else
	{
		static const DataType	s_types[]	= { TYPE_FLOAT,	TYPE_INT,	TYPE_UINT	};
		static const float		s_weights[] = { 3.0,		1.0,		1.0			};
		const int				size		= rnd.getInt(1, 4);
		const DataType			scalarType	= rnd.chooseWeighted<DataType>(
			DE_ARRAY_BEGIN(s_types), DE_ARRAY_END(s_types), DE_ARRAY_BEGIN(s_weights));
		return getDataTypeVector(scalarType, size);
	}

	DE_ASSERT(!"Impossible");
	return TYPE_INVALID;
}

VaryingInterpolation randomInterpolation (Random& rnd)
{
	static const VaryingInterpolation s_validInterpolations[] =
	{
		VARYINGINTERPOLATION_SMOOTH,
		VARYINGINTERPOLATION_FLAT,
		VARYINGINTERPOLATION_CENTROID,
		VARYINGINTERPOLATION_DEFAULT,
	};
	return s_validInterpolations[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_validInterpolations)-1)];
}

glu::Interpolation getGluInterpolation (VaryingInterpolation interpolation)
{
	switch (interpolation)
	{
		case VARYINGINTERPOLATION_SMOOTH:	return glu::INTERPOLATION_SMOOTH;
		case VARYINGINTERPOLATION_FLAT:		return glu::INTERPOLATION_FLAT;
		case VARYINGINTERPOLATION_CENTROID:	return glu::INTERPOLATION_CENTROID;
		case VARYINGINTERPOLATION_DEFAULT:	return glu::INTERPOLATION_LAST;		//!< Last means no qualifier, i.e. default
		default:
			DE_ASSERT(!"Invalid interpolation");
			return glu::INTERPOLATION_LAST;
	}
}

// used only for debug sanity checks
#if defined(DE_DEBUG)
VaryingInterpolation getVaryingInterpolation (glu::Interpolation interpolation)
{
	switch (interpolation)
	{
		case glu::INTERPOLATION_SMOOTH:		return VARYINGINTERPOLATION_SMOOTH;
		case glu::INTERPOLATION_FLAT:		return VARYINGINTERPOLATION_FLAT;
		case glu::INTERPOLATION_CENTROID:	return VARYINGINTERPOLATION_CENTROID;
		case glu::INTERPOLATION_LAST:		return VARYINGINTERPOLATION_DEFAULT;		//!< Last means no qualifier, i.e. default
		default:
			DE_ASSERT(!"Invalid interpolation");
			return VARYINGINTERPOLATION_LAST;
	}
}
#endif

enum BindingKind
{
	BINDING_NAME,
	BINDING_LOCATION,
	BINDING_LAST
};

BindingKind randomBinding (Random& rnd)
{
	return rnd.getBool() ? BINDING_LOCATION : BINDING_NAME;
}

void printInputColor (ostringstream& oss, const VariableDeclaration& input)
{
	using namespace glu;

	const DataType	basicType	= input.varType.getBasicType();
	string			exp			= input.name;

	switch (getDataTypeScalarType(basicType))
	{
		case TYPE_FLOAT:
			break;

		case TYPE_INT:
		case TYPE_UINT:
		{
			DataType floatType = getDataTypeFloatScalars(basicType);
			exp = string() + "(" + getDataTypeName(floatType) + "(" + exp + ") / 255.0" + ")";
			break;
		}

		default:
			DE_ASSERT(!"Impossible");
	}

	if (isDataTypeScalarOrVector(basicType))
	{
		switch (getDataTypeScalarSize(basicType))
		{
			case 1:
				oss << "hsv(vec3(" << exp << ", 1.0, 1.0))";
				break;
			case 2:
				oss << "hsv(vec3(" << exp << ", 1.0))";
				break;
			case 3:
				oss << "vec4(" << exp << ", 1.0)";
				break;
			case 4:
				oss << exp;
				break;
			default:
				DE_ASSERT(!"Impossible");
		}
	}
	else if (isDataTypeMatrix(basicType))
	{
		int	rows	= getDataTypeMatrixNumRows(basicType);
		int	columns	= getDataTypeMatrixNumColumns(basicType);

		if (rows == columns)
			oss << "hsv(vec3(determinant(" << exp << ")))";
		else
		{
			if (rows != 3 && columns >= 3)
			{
				exp = "transpose(" + exp + ")";
				std::swap(rows, columns);
			}
			exp = exp + "[0]";
			if (rows > 3)
				exp = exp + ".xyz";
			oss << "hsv(" << exp << ")";
		}
	}
	else
		DE_ASSERT(!"Impossible");
}

// Representation for the varyings between vertex and fragment shaders

struct VaryingParams
{
	VaryingParams			(void)
		: count				(0)
		, type				(glu::TYPE_LAST)
		, binding			(BINDING_LAST)
		, vtxInterp			(VARYINGINTERPOLATION_LAST)
		, frgInterp			(VARYINGINTERPOLATION_LAST) {}

	int						count;
	DataType				type;
	BindingKind				binding;
	VaryingInterpolation	vtxInterp;
	VaryingInterpolation	frgInterp;
};

struct VaryingInterface
{
	vector<VariableDeclaration>	vtxOutputs;
	vector<VariableDeclaration>	frgInputs;
};

// Generate corresponding input and output variable declarations that may vary
// in compatible ways.

VaryingInterpolation chooseInterpolation (VaryingInterpolation param, DataType type, Random& rnd)
{
	if (glu::getDataTypeScalarType(type) != glu::TYPE_FLOAT)
		return VARYINGINTERPOLATION_FLAT;

	if (param == VARYINGINTERPOLATION_RANDOM)
		return randomInterpolation(rnd);

	return param;
}

bool isSSOCompatibleInterpolation (VaryingInterpolation vertexInterpolation, VaryingInterpolation fragmentInterpolation)
{
	// interpolations must be fully specified
	DE_ASSERT(vertexInterpolation != VARYINGINTERPOLATION_RANDOM);
	DE_ASSERT(vertexInterpolation < VARYINGINTERPOLATION_LAST);
	DE_ASSERT(fragmentInterpolation != VARYINGINTERPOLATION_RANDOM);
	DE_ASSERT(fragmentInterpolation < VARYINGINTERPOLATION_LAST);

	// interpolation can only be either smooth or flat. Auxiliary storage does not matter.
	const bool isSmoothVtx =    (vertexInterpolation == VARYINGINTERPOLATION_SMOOTH)      || //!< trivial
	                            (vertexInterpolation == VARYINGINTERPOLATION_DEFAULT)     || //!< default to smooth
	                            (vertexInterpolation == VARYINGINTERPOLATION_CENTROID);      //!< default to smooth, ignore storage
	const bool isSmoothFrag =   (fragmentInterpolation == VARYINGINTERPOLATION_SMOOTH)    || //!< trivial
	                            (fragmentInterpolation == VARYINGINTERPOLATION_DEFAULT)   || //!< default to smooth
	                            (fragmentInterpolation == VARYINGINTERPOLATION_CENTROID);    //!< default to smooth, ignore storage
	// Khronos bug #12630: flat / smooth qualifiers must match in SSO
	return isSmoothVtx == isSmoothFrag;
}

VaryingInterface genVaryingInterface (const VaryingParams&		params,
									  Random&					rnd)
{
	using namespace	glu;

	VaryingInterface	ret;
	int					offset = 0;

	for (int varNdx = 0; varNdx < params.count; ++varNdx)
	{
		const BindingKind			binding			= ((params.binding == BINDING_LAST)
													   ? randomBinding(rnd) : params.binding);
		const DataType				type			= ((params.type == TYPE_LAST)
													   ? randomType(rnd) : params.type);
		const VaryingInterpolation	vtxInterp		= chooseInterpolation(params.vtxInterp, type, rnd);
		const VaryingInterpolation	frgInterp		= chooseInterpolation(params.frgInterp, type, rnd);
		const VaryingInterpolation	vtxCompatInterp	= (isSSOCompatibleInterpolation(vtxInterp, frgInterp))
													   ? (vtxInterp) : (frgInterp);
		const int					loc				= ((binding == BINDING_LOCATION) ? offset : -1);
		const string				ndxStr			= de::toString(varNdx);
		const string				vtxName			= ((binding == BINDING_NAME)
													   ? "var" + ndxStr : "vtxVar" + ndxStr);
		const string				frgName			= ((binding == BINDING_NAME)
													   ? "var" + ndxStr : "frgVar" + ndxStr);
		const VarType				varType			(type, PRECISION_HIGHP);

		offset += getDataTypeNumLocations(type);

		// Over 16 locations aren't necessarily supported, so halt here.
		if (offset > 16)
			break;

		ret.vtxOutputs.push_back(
			VariableDeclaration(varType, vtxName, STORAGE_OUT, getGluInterpolation(vtxCompatInterp), loc));
		ret.frgInputs.push_back(
			VariableDeclaration(varType, frgName, STORAGE_IN, getGluInterpolation(frgInterp), loc));
	}

	return ret;
}

// Create vertex output variable declarations that are maximally compatible
// with the fragment input variables.

vector<VariableDeclaration> varyingCompatVtxOutputs (const VaryingInterface& varyings)
{
	vector<VariableDeclaration> outputs = varyings.vtxOutputs;

	for (size_t i = 0; i < outputs.size(); ++i)
	{
		outputs[i].interpolation = varyings.frgInputs[i].interpolation;
		outputs[i].name = varyings.frgInputs[i].name;
	}

	return outputs;
}

// Shader source generation

void printFloat (ostringstream& oss, double d)
{
	oss.setf(oss.fixed | oss.internal);
	oss.precision(4);
	oss.width(7);
	oss << d;
}

void printFloatDeclaration (ostringstream&	oss,
							const string&	varName,
							bool			uniform,
							GLfloat			value		= 0.0)
{
	using namespace glu;

	const VarType	varType	(TYPE_FLOAT, PRECISION_HIGHP);

	if (uniform)
		oss << VariableDeclaration(varType, varName, STORAGE_UNIFORM) << ";\n";
	else
		oss << VariableDeclaration(varType, varName, STORAGE_CONST)
			<< " = " << de::floatToString(value, 6) << ";\n";
}

void printRandomInitializer (ostringstream& oss, DataType type, Random& rnd)
{
	using namespace glu;
	const int		size	= getDataTypeScalarSize(type);

	if (size > 0)
		oss << getDataTypeName(type) << "(";

	for (int i = 0; i < size; ++i)
	{
		oss << (i == 0 ? "" : ", ");
		switch (getDataTypeScalarType(type))
		{
			case TYPE_FLOAT:
				printFloat(oss, rnd.getInt(0, 16) / 16.0);
				break;

			case TYPE_INT:
			case TYPE_UINT:
				oss << rnd.getInt(0, 255);
				break;

			case TYPE_BOOL:
				oss << (rnd.getBool() ? "true" : "false");
				break;

			default:
				DE_ASSERT(!"Impossible");
		}
	}

	if (size > 0)
		oss << ")";
}

string genVtxShaderSrc (deUint32							seed,
						const vector<VariableDeclaration>&	outputs,
						const string&						varName,
						bool								uniform,
						float								value = 0.0)
{
	ostringstream		oss;
	Random				rnd								(seed);
	enum {				NUM_COMPONENTS					= 2 };
	static const int	s_quadrants[][NUM_COMPONENTS]	= { {1, 1}, {-1, 1}, {1, -1} };

	oss << "#version 310 es\n";

	printFloatDeclaration(oss, varName, uniform, value);

	for (vector<VariableDeclaration>::const_iterator it = outputs.begin();
		 it != outputs.end(); ++it)
		oss << *it << ";\n";

	oss << "const vec2 triangle[3] = vec2[3](\n";

	for (int vertexNdx = 0; vertexNdx < DE_LENGTH_OF_ARRAY(s_quadrants); ++vertexNdx)
	{
		oss << "\tvec2(";

		for (int componentNdx = 0; componentNdx < NUM_COMPONENTS; ++componentNdx)
		{
			printFloat(oss, s_quadrants[vertexNdx][componentNdx] * rnd.getInt(4,16) / 16.0);
			oss << (componentNdx < 1 ? ", " : "");
		}

		oss << ")" << (vertexNdx < 2 ? "," : "") << "\n";
	}
	oss << ");\n";


	for (vector<VariableDeclaration>::const_iterator it = outputs.begin();
		 it != outputs.end(); ++it)
	{
		const DataType	type		= it->varType.getBasicType();
		const string	typeName	= glu::getDataTypeName(type);

		oss << "const " << typeName << " " << it->name << "Inits[3] = "
			<< typeName << "[3](\n";
		for (int i = 0; i < 3; ++i)
		{
			oss << (i == 0 ? "\t" : ",\n\t");
			printRandomInitializer(oss, type, rnd);
		}
		oss << ");\n";
	}

	oss << "void main (void)\n"
		<< "{\n"
		<< "\tgl_Position = vec4(" << varName << " * triangle[gl_VertexID], 0.0, 1.0);\n";

	for (vector<VariableDeclaration>::const_iterator it = outputs.begin();
		 it != outputs.end(); ++it)
		oss << "\t" << it->name << " = " << it->name << "Inits[gl_VertexID];\n";

	oss << "}\n";

	return oss.str();
}

string genFrgShaderSrc (deUint32							seed,
						const vector<VariableDeclaration>&	inputs,
						const string&						varName,
						bool								uniform,
						float								value = 0.0)
{
	Random				rnd		(seed);
	ostringstream		oss;

	oss.precision(4);
	oss.width(7);
	oss << "#version 310 es\n";

	oss << "precision highp float;\n";

	oss << "out vec4 fragColor;\n";

	printFloatDeclaration(oss, varName, uniform, value);

	for (vector<VariableDeclaration>::const_iterator it = inputs.begin();
		 it != inputs.end(); ++it)
		oss << *it << ";\n";

	// glsl % isn't defined for negative numbers
	oss << "int imod (int n, int d)" << "\n"
		<< "{" << "\n"
		<< "\t" << "return (n < 0 ? d - 1 - (-1 - n) % d : n % d);" << "\n"
		<< "}" << "\n";

	oss << "vec4 hsv (vec3 hsv)"
		<< "{" << "\n"
		<< "\tfloat h = hsv.x * 3.0;\n"
		<< "\tfloat r = max(0.0, 1.0 - h) + max(0.0, h - 2.0);\n"
		<< "\tfloat g = max(0.0, 1.0 - abs(h - 1.0));\n"
		<< "\tfloat b = max(0.0, 1.0 - abs(h - 2.0));\n"
		<< "\tvec3 hs = mix(vec3(1.0), vec3(r, g, b), hsv.y);\n"
		<< "\treturn vec4(hsv.z * hs, 1.0);\n"
		<< "}\n";

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

	oss << "\t" << "fragColor = vec4(vec3(" << varName << "), 1.0);" << "\n";

	if (inputs.size() > 0)
	{
		oss << "\t"
			<< "switch (imod(int(0.5 * (";

		printFloat(oss, rnd.getFloat(0.5f, 2.0f));
		oss << " * gl_FragCoord.x - ";

		printFloat(oss, rnd.getFloat(0.5f, 2.0f));
		oss << " * gl_FragCoord.y)), "
			<< inputs.size() << "))" << "\n"
			<< "\t" << "{" << "\n";

		for (size_t i = 0; i < inputs.size(); ++i)
		{
			oss << "\t\t" << "case " << i << ":" << "\n"
				<< "\t\t\t" << "fragColor *= ";

			printInputColor(oss, inputs[i]);

			oss << ";" << "\n"
				<< "\t\t\t" << "break;" << "\n";
		}

		oss << "\t\t" << "case " << inputs.size() << ":\n"
			<< "\t\t\t" << "fragColor = vec4(1.0, 0.0, 1.0, 1.0);" << "\n";
		oss << "\t\t\t" << "break;" << "\n";

		oss << "\t\t" << "case -1:\n"
			<< "\t\t\t" << "fragColor = vec4(1.0, 1.0, 0.0, 1.0);" << "\n";
		oss << "\t\t\t" << "break;" << "\n";

		oss << "\t\t" << "default:" << "\n"
			<< "\t\t\t" << "fragColor = vec4(1.0, 1.0, 0.0, 1.0);" << "\n";

		oss << "\t" << "}\n";

	}

	oss << "}\n";

	return oss.str();
}

// ProgramWrapper

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

	virtual GLuint	getProgramName			(void) = 0;
	virtual void	writeToLog				(TestLog& log) = 0;
};

class ShaderProgramWrapper : public ProgramWrapper
{
public:
					ShaderProgramWrapper	(const RenderContext&	renderCtx,
											 const ProgramSources&	sources)
						: m_shaderProgram	(renderCtx, sources) {}
					~ShaderProgramWrapper	(void) {}

	GLuint			getProgramName			(void) { return m_shaderProgram.getProgram(); }
	ShaderProgram&	getShaderProgram		(void) { return m_shaderProgram; }
	void			writeToLog				(TestLog& log) { log << m_shaderProgram; }

private:
	ShaderProgram	m_shaderProgram;
};

class RawProgramWrapper : public ProgramWrapper
{
public:
					RawProgramWrapper		(const RenderContext&	renderCtx,
											 GLuint					programName,
											 ShaderType				shaderType,
											 const string&			source)
						: m_program			(renderCtx, programName)
						, m_shaderType		(shaderType)
						, m_source			(source) {}
					~RawProgramWrapper		(void) {}

	GLuint			getProgramName			(void) { return m_program.getProgram(); }
	Program&		getProgram				(void) { return m_program; }
	void			writeToLog				(TestLog& log);

private:
	Program			m_program;
	ShaderType		m_shaderType;
	const string	m_source;
};

void RawProgramWrapper::writeToLog (TestLog& log)
{
	const string	info	= m_program.getInfoLog();
	qpShaderType	qpType	= glu::getLogShaderType(m_shaderType);

	log << TestLog::ShaderProgram(true, info)
		<< TestLog::Shader(qpType, m_source,
						   true, "[Shader created by glCreateShaderProgramv()]")
		<< TestLog::EndShaderProgram;
}

// ProgramParams

struct ProgramParams
{
	ProgramParams (deUint32 vtxSeed_, GLfloat vtxScale_, deUint32 frgSeed_, GLfloat frgScale_)
		: vtxSeed	(vtxSeed_)
		, vtxScale	(vtxScale_)
		, frgSeed	(frgSeed_)
		, frgScale	(frgScale_) {}
	deUint32	vtxSeed;
	GLfloat		vtxScale;
	deUint32	frgSeed;
	GLfloat		frgScale;
};

ProgramParams genProgramParams (Random& rnd)
{
	const deUint32	vtxSeed		= rnd.getUint32();
	const GLfloat	vtxScale	= rnd.getInt(8, 16) / 16.0f;
	const deUint32	frgSeed		= rnd.getUint32();
	const GLfloat	frgScale	= rnd.getInt(0, 16) / 16.0f;

	return ProgramParams(vtxSeed, vtxScale, frgSeed, frgScale);
}

// TestParams

struct TestParams
{
	bool					initSingle;
	bool					switchVtx;
	bool					switchFrg;
	bool					useUniform;
	bool					useSameName;
	bool					useCreateHelper;
	bool					useProgramUniform;
	VaryingParams			varyings;
};

deUint32 paramsSeed (const TestParams& params)
{
	deUint32 paramCode	= (params.initSingle			<< 0 |
						   params.switchVtx				<< 1 |
						   params.switchFrg				<< 2 |
						   params.useUniform			<< 3 |
						   params.useSameName			<< 4 |
						   params.useCreateHelper		<< 5 |
						   params.useProgramUniform		<< 6);

	paramCode = deUint32Hash(paramCode) + params.varyings.count;
	paramCode = deUint32Hash(paramCode) + params.varyings.type;
	paramCode = deUint32Hash(paramCode) + params.varyings.binding;
	paramCode = deUint32Hash(paramCode) + params.varyings.vtxInterp;
	paramCode = deUint32Hash(paramCode) + params.varyings.frgInterp;

	return deUint32Hash(paramCode);
}

string paramsCode (const TestParams& params)
{
	using namespace glu;

	ostringstream oss;

	oss << (params.initSingle ? "1" : "2")
		<< (params.switchVtx ? "v" : "")
		<< (params.switchFrg ? "f" : "")
		<< (params.useProgramUniform ? "p" : "")
		<< (params.useUniform ? "u" : "")
		<< (params.useSameName ? "s" : "")
		<< (params.useCreateHelper ? "c" : "")
		 << de::toString(params.varyings.count)
		 << (params.varyings.binding == BINDING_NAME ? "n" :
			 params.varyings.binding == BINDING_LOCATION ? "l" :
			 params.varyings.binding == BINDING_LAST ? "r" :
			"")
		 << (params.varyings.vtxInterp == VARYINGINTERPOLATION_SMOOTH ? "m" :
			 params.varyings.vtxInterp == VARYINGINTERPOLATION_CENTROID ? "e" :
			 params.varyings.vtxInterp == VARYINGINTERPOLATION_FLAT ? "a" :
			 params.varyings.vtxInterp == VARYINGINTERPOLATION_RANDOM ? "r" :
			"o")
		 << (params.varyings.frgInterp == VARYINGINTERPOLATION_SMOOTH ? "m" :
			 params.varyings.frgInterp == VARYINGINTERPOLATION_CENTROID ? "e" :
			 params.varyings.frgInterp == VARYINGINTERPOLATION_FLAT ? "a" :
			 params.varyings.frgInterp == VARYINGINTERPOLATION_RANDOM ? "r" :
			"o");
	return oss.str();
}

bool paramsValid (const TestParams& params)
{
	using namespace glu;

	// Final pipeline has a single program?
	if (params.initSingle)
	{
		// Cannot have conflicting names for uniforms or constants
		if (params.useSameName)
			return false;

		// CreateShaderProgram would never get called
		if (!params.switchVtx && !params.switchFrg && params.useCreateHelper)
			return false;

		// Must switch either all or nothing
		if (params.switchVtx != params.switchFrg)
			return false;
	}

	// ProgramUniform would never get called
	if (params.useProgramUniform && !params.useUniform)
		return false;

	// Interpolation is meaningless if we don't use an in/out variable.
	if (params.varyings.count == 0 &&
		!(params.varyings.vtxInterp == VARYINGINTERPOLATION_LAST &&
		  params.varyings.frgInterp == VARYINGINTERPOLATION_LAST))
		return false;

	// Mismatch by flat / smooth is not allowed. See Khronos bug #12630
	// \note: iterpolations might be RANDOM, causing generated varyings potentially match / mismatch anyway.
	//        This is checked later on. Here, we just make sure that we don't force the generator to generate
	//        only invalid varying configurations, i.e. there exists a valid varying configuration for this
	//        test param config.
	if ((params.varyings.vtxInterp != VARYINGINTERPOLATION_RANDOM) &&
		(params.varyings.frgInterp != VARYINGINTERPOLATION_RANDOM) &&
		(params.varyings.vtxInterp == VARYINGINTERPOLATION_FLAT) != (params.varyings.frgInterp == VARYINGINTERPOLATION_FLAT))
		return false;

	return true;
}

// used only for debug sanity checks
#if defined(DE_DEBUG)
bool varyingsValid (const VaryingInterface& varyings)
{
	for (int ndx = 0; ndx < (int)varyings.vtxOutputs.size(); ++ndx)
	{
		const VaryingInterpolation vertexInterpolation		= getVaryingInterpolation(varyings.vtxOutputs[ndx].interpolation);
		const VaryingInterpolation fragmentInterpolation	= getVaryingInterpolation(varyings.frgInputs[ndx].interpolation);

		if (!isSSOCompatibleInterpolation(vertexInterpolation, fragmentInterpolation))
			return false;
	}

	return true;
}
#endif

void logParams (TestLog& log, const TestParams& params)
{
	// We don't log operational details here since those are shown
	// in the log messages during execution.
	MessageBuilder msg = log.message();

	msg << "Pipeline configuration:\n";

	msg << "Vertex and fragment shaders have "
		<< (params.useUniform ? "uniform" : "constant") << "s with "
		<< (params.useSameName ? "the same name" : "different names") << ".\n";

	if (params.varyings.count == 0)
		msg << "There are no varyings.\n";
	else
	{
		if (params.varyings.count == 1)
			msg << "There is one varying.\n";
		else
			msg << "There are " << params.varyings.count << " varyings.\n";

		if (params.varyings.type == glu::TYPE_LAST)
			msg << "Varyings are of random types.\n";
		else
			msg << "Varyings are of type '"
				<< glu::getDataTypeName(params.varyings.type) << "'.\n";

		msg << "Varying outputs and inputs correspond ";
		switch (params.varyings.binding)
		{
			case BINDING_NAME:
				msg << "by name.\n";
				break;
			case BINDING_LOCATION:
				msg << "by location.\n";
				break;
			case BINDING_LAST:
				msg << "randomly either by name or by location.\n";
				break;
			default:
				DE_ASSERT(!"Impossible");
		}

		msg << "In the vertex shader the varyings are qualified ";
		if (params.varyings.vtxInterp == VARYINGINTERPOLATION_DEFAULT)
			msg << "with no interpolation qualifiers.\n";
		else if (params.varyings.vtxInterp == VARYINGINTERPOLATION_RANDOM)
			msg << "with a random interpolation qualifier.\n";
		else
			msg << "'" << glu::getInterpolationName(getGluInterpolation(params.varyings.vtxInterp)) << "'.\n";

		msg << "In the fragment shader the varyings are qualified ";
		if (params.varyings.frgInterp == VARYINGINTERPOLATION_DEFAULT)
			msg << "with no interpolation qualifiers.\n";
		else if (params.varyings.frgInterp == VARYINGINTERPOLATION_RANDOM)
			msg << "with a random interpolation qualifier.\n";
		else
			msg << "'" << glu::getInterpolationName(getGluInterpolation(params.varyings.frgInterp)) << "'.\n";
	}

	msg << TestLog::EndMessage;

	log.writeMessage("");
}

TestParams genParams (deUint32 seed)
{
	Random		rnd		(seed);
	TestParams	params;
	int			tryNdx	= 0;

	do
	{
		params.initSingle			= rnd.getBool();
		params.switchVtx			= rnd.getBool();
		params.switchFrg			= rnd.getBool();
		params.useUniform			= rnd.getBool();
		params.useProgramUniform	= params.useUniform && rnd.getBool();
		params.useCreateHelper		= rnd.getBool();
		params.useSameName			= rnd.getBool();
		{
			int i = rnd.getInt(-1, 3);
			params.varyings.count = (i == -1 ? 0 : 1 << i);
		}
		if (params.varyings.count > 0)
		{
			params.varyings.type		= glu::TYPE_LAST;
			params.varyings.binding		= BINDING_LAST;
			params.varyings.vtxInterp	= VARYINGINTERPOLATION_RANDOM;
			params.varyings.frgInterp	= VARYINGINTERPOLATION_RANDOM;
		}
		else
		{
			params.varyings.type		= glu::TYPE_INVALID;
			params.varyings.binding		= BINDING_LAST;
			params.varyings.vtxInterp	= VARYINGINTERPOLATION_LAST;
			params.varyings.frgInterp	= VARYINGINTERPOLATION_LAST;
		}

		tryNdx += 1;
	} while (!paramsValid(params) && tryNdx < 16);

	DE_ASSERT(paramsValid(params));

	return params;
}

// Program pipeline wrapper that retains references to component programs.

struct Pipeline
{
								Pipeline			(MovePtr<ProgramPipeline>	pipeline_,
													 MovePtr<ProgramWrapper>	fullProg_,
													 MovePtr<ProgramWrapper>	vtxProg_,
													 MovePtr<ProgramWrapper>	frgProg_)
									: pipeline	(pipeline_)
									, fullProg	(fullProg_)
									, vtxProg	(vtxProg_)
									, frgProg	(frgProg_) {}

	ProgramWrapper&				getVertexProgram	(void) const
	{
		return vtxProg ? *vtxProg : *fullProg;
	}

	ProgramWrapper&				getFragmentProgram	(void) const
	{
		return frgProg ? *frgProg : *fullProg;
	}

	UniquePtr<ProgramPipeline>	pipeline;
	UniquePtr<ProgramWrapper>	fullProg;
	UniquePtr<ProgramWrapper>	vtxProg;
	UniquePtr<ProgramWrapper>	frgProg;
};

void logPipeline(TestLog& log, const Pipeline& pipeline)
{
	ProgramWrapper&	vtxProg	= pipeline.getVertexProgram();
	ProgramWrapper&	frgProg	= pipeline.getFragmentProgram();

	log.writeMessage("// Failed program pipeline:");
	if (&vtxProg == &frgProg)
	{
		log.writeMessage("// Common program for both vertex and fragment stages:");
		vtxProg.writeToLog(log);
	}
	else
	{
		log.writeMessage("// Vertex stage program:");
		vtxProg.writeToLog(log);
		log.writeMessage("// Fragment stage program:");
		frgProg.writeToLog(log);
	}
}

// Rectangle

struct Rectangle
{
			Rectangle	(int x_, int y_, int width_, int height_)
				: x			(x_)
				, y			(y_)
				, width		(width_)
				, height	(height_) {}
	int	x;
	int	y;
	int	width;
	int	height;
};

void setViewport (const RenderContext& renderCtx, const Rectangle& rect)
{
	renderCtx.getFunctions().viewport(rect.x, rect.y, rect.width, rect.height);
}

void readRectangle (const RenderContext& renderCtx, const Rectangle& rect, Surface& dst)
{
	dst.setSize(rect.width, rect.height);
	glu::readPixels(renderCtx, rect.x, rect.y, dst.getAccess());
}

Rectangle randomViewport (const RenderContext& ctx, Random& rnd,
						  GLint maxWidth, GLint maxHeight)
{
	const RenderTarget&	target	= ctx.getRenderTarget();
	GLint				width	= de::min(target.getWidth(), maxWidth);
	GLint				xOff	= rnd.getInt(0, target.getWidth() - width);
	GLint				height	= de::min(target.getHeight(), maxHeight);
	GLint				yOff	= rnd.getInt(0, target.getHeight() - height);

	return Rectangle(xOff, yOff, width, height);
}

// SeparateShaderTest

class SeparateShaderTest : public TestCase, private CallLogWrapper
{
public:
	typedef	void			(SeparateShaderTest::*TestFunc)
														(MovePtr<Pipeline>&		pipeOut);

							SeparateShaderTest			(Context&				ctx,
														 const string&			name,
														 const string&			description,
														 int					iterations,
														 const TestParams&		params,
														 TestFunc				testFunc);

	IterateResult			iterate						(void);

	void					testPipelineRendering		(MovePtr<Pipeline>&		pipeOut);
	void					testCurrentProgPriority		(MovePtr<Pipeline>&		pipeOut);
	void					testActiveProgramUniform	(MovePtr<Pipeline>&		pipeOut);
	void					testPipelineQueryActive		(MovePtr<Pipeline>&		pipeOut);
	void					testPipelineQueryPrograms	(MovePtr<Pipeline>&		pipeOut);

private:
	TestLog&				log							(void);
	const RenderContext&	getRenderContext			(void);

	void					setUniform					(ProgramWrapper&		program,
														 const string&			uniformName,
														 GLfloat				value,
														 bool					useProgramUni);

	void					drawSurface					(Surface&				dst,
														 deUint32				seed = 0);

	MovePtr<ProgramWrapper>	createShaderProgram			(const string*			vtxSource,
														 const string*			frgSource,
														 bool					separable);

	MovePtr<ProgramWrapper>	createSingleShaderProgram	(ShaderType			shaderType,
														 const string&			src);

	MovePtr<Pipeline>		createPipeline				(const ProgramParams&	pp);

	MovePtr<ProgramWrapper>	createReferenceProgram		(const ProgramParams&	pp);

	int						m_iterations;
	int						m_currentIteration;
	TestParams				m_params;
	TestFunc				m_testFunc;
	Random					m_rnd;
	ResultCollector			m_status;
	VaryingInterface		m_varyings;

	// Per-iteration state required for logging on exception
	MovePtr<ProgramWrapper>	m_fullProg;
	MovePtr<ProgramWrapper>	m_vtxProg;
	MovePtr<ProgramWrapper>	m_frgProg;

};

const RenderContext& SeparateShaderTest::getRenderContext (void)
{
	return m_context.getRenderContext();
}

TestLog& SeparateShaderTest::log (void)
{
	return m_testCtx.getLog();
}

SeparateShaderTest::SeparateShaderTest (Context&			ctx,
										const string&		name,
										const string&		description,
										int					iterations,
										const TestParams&	params,
										TestFunc			testFunc)
	: TestCase			(ctx, name.c_str(), description.c_str())
	, CallLogWrapper	(ctx.getRenderContext().getFunctions(), log())
	, m_iterations		(iterations)
	, m_currentIteration(0)
	, m_params			(params)
	, m_testFunc		(testFunc)
	, m_rnd				(paramsSeed(params))
	, m_status			(log(), "// ")
	, m_varyings		(genVaryingInterface(params.varyings, m_rnd))
{
	DE_ASSERT(paramsValid(params));
	DE_ASSERT(varyingsValid(m_varyings));
}

MovePtr<ProgramWrapper> SeparateShaderTest::createShaderProgram (const string*	vtxSource,
																 const string*	frgSource,
																 bool			separable)
{
	ProgramSources sources;

	if (vtxSource != DE_NULL)
		sources << VertexSource(*vtxSource);
	if (frgSource != DE_NULL)
		sources << FragmentSource(*frgSource);
	sources << ProgramSeparable(separable);

	MovePtr<ShaderProgramWrapper> wrapper (new ShaderProgramWrapper(getRenderContext(),
																	sources));
	if (!wrapper->getShaderProgram().isOk())
	{
		log().writeMessage("Couldn't create shader program");
		wrapper->writeToLog(log());
		TCU_FAIL("Couldn't create shader program");
	}

	return MovePtr<ProgramWrapper>(wrapper.release());
}

MovePtr<ProgramWrapper> SeparateShaderTest::createSingleShaderProgram (ShaderType shaderType,
																	   const string& src)
{
	const RenderContext&	renderCtx	= getRenderContext();

	if (m_params.useCreateHelper)
	{
		const char*	const	srcStr		= src.c_str();
		const GLenum		glType		= glu::getGLShaderType(shaderType);
		const GLuint		programName	= glCreateShaderProgramv(glType, 1, &srcStr);

		if (glGetError() != GL_NO_ERROR || programName == 0)
		{
			qpShaderType qpType = glu::getLogShaderType(shaderType);

			log() << TestLog::Message << "glCreateShaderProgramv() failed"
				  << TestLog::EndMessage
				  << TestLog::ShaderProgram(false, "[glCreateShaderProgramv() failed]")
				  << TestLog::Shader(qpType, src,
									 false, "[glCreateShaderProgramv() failed]")
				  << TestLog::EndShaderProgram;
			TCU_FAIL("glCreateShaderProgramv() failed");
		}

		RawProgramWrapper* const	wrapper	= new RawProgramWrapper(renderCtx, programName,
																	shaderType, src);
		MovePtr<ProgramWrapper>		wrapperPtr(wrapper);
		Program&					program = wrapper->getProgram();

		if (!program.getLinkStatus())
		{
			log().writeMessage("glCreateShaderProgramv() failed at linking");
			wrapper->writeToLog(log());
			TCU_FAIL("glCreateShaderProgram() failed at linking");
		}
		return wrapperPtr;
	}
	else
	{
		switch (shaderType)
		{
			case glu::SHADERTYPE_VERTEX:
				return createShaderProgram(&src, DE_NULL, true);
			case glu::SHADERTYPE_FRAGMENT:
				return createShaderProgram(DE_NULL, &src, true);
			default:
				DE_ASSERT(!"Impossible case");
		}
	}
	return MovePtr<ProgramWrapper>(); // Shut up compiler warnings.
}

void SeparateShaderTest::setUniform (ProgramWrapper&	program,
									 const string&		uniformName,
									 GLfloat			value,
									 bool				useProgramUniform)
{
	const GLuint		progName	= program.getProgramName();
	const GLint			location	= glGetUniformLocation(progName, uniformName.c_str());
	MessageBuilder		msg			= log().message();

	msg << "// Set program " << progName << "'s uniform '" << uniformName << "' to " << value;
	if (useProgramUniform)
	{
		msg << " using glProgramUniform1f";
		glProgramUniform1f(progName, location, value);
	}
	else
	{
		msg << " using glUseProgram and glUniform1f";
		glUseProgram(progName);
		glUniform1f(location, value);
		glUseProgram(0);
	}
	msg << TestLog::EndMessage;
}

MovePtr<Pipeline> SeparateShaderTest::createPipeline (const ProgramParams& pp)
{
	const bool		useUniform	= m_params.useUniform;
	const string	vtxName		= m_params.useSameName ? "scale" : "vtxScale";
	const deUint32	initVtxSeed	= m_params.switchVtx ? m_rnd.getUint32() : pp.vtxSeed;

	const string	frgName		= m_params.useSameName ? "scale" : "frgScale";
	const deUint32	initFrgSeed	= m_params.switchFrg ? m_rnd.getUint32() : pp.frgSeed;
	const string	frgSource	= genFrgShaderSrc(initFrgSeed, m_varyings.frgInputs,
												  frgName, useUniform, pp.frgScale);

	const RenderContext&		renderCtx	= getRenderContext();
	MovePtr<ProgramPipeline>	pipeline	(new ProgramPipeline(renderCtx));
	MovePtr<ProgramWrapper>		fullProg;
	MovePtr<ProgramWrapper>		vtxProg;
	MovePtr<ProgramWrapper>		frgProg;

	// We cannot allow a situation where we have a single program with a
	// single uniform, because then the vertex and fragment shader uniforms
	// would not be distinct in the final pipeline, and we are going to test
	// that altering one uniform will not affect the other.
	DE_ASSERT(!(m_params.initSingle	&& m_params.useSameName &&
				!m_params.switchVtx && !m_params.switchFrg));

	if (m_params.initSingle)
	{
		string vtxSource = genVtxShaderSrc(initVtxSeed,
										   varyingCompatVtxOutputs(m_varyings),
										   vtxName, useUniform, pp.vtxScale);
		fullProg = createShaderProgram(&vtxSource, &frgSource, true);
		pipeline->useProgramStages(GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT,
								   fullProg->getProgramName());
		log() << TestLog::Message
			  << "// Created pipeline " << pipeline->getPipeline()
			  << " with two-shader program " << fullProg->getProgramName()
			  << TestLog::EndMessage;
	}
	else
	{
		string vtxSource = genVtxShaderSrc(initVtxSeed, m_varyings.vtxOutputs,
										   vtxName, useUniform, pp.vtxScale);
		vtxProg = createSingleShaderProgram(glu::SHADERTYPE_VERTEX, vtxSource);
		pipeline->useProgramStages(GL_VERTEX_SHADER_BIT, vtxProg->getProgramName());

		frgProg = createSingleShaderProgram(glu::SHADERTYPE_FRAGMENT, frgSource);
		pipeline->useProgramStages(GL_FRAGMENT_SHADER_BIT, frgProg->getProgramName());

		log() << TestLog::Message
			  << "// Created pipeline " << pipeline->getPipeline()
			  << " with vertex program " << vtxProg->getProgramName()
			  << " and fragment program " << frgProg->getProgramName()
			  << TestLog::EndMessage;
	}

	m_status.check(pipeline->isValid(),
				   "Pipeline is invalid after initialization");

	if (m_params.switchVtx)
	{
		string newSource = genVtxShaderSrc(pp.vtxSeed, m_varyings.vtxOutputs,
										   vtxName, useUniform, pp.vtxScale);
		vtxProg = createSingleShaderProgram(glu::SHADERTYPE_VERTEX, newSource);
		pipeline->useProgramStages(GL_VERTEX_SHADER_BIT, vtxProg->getProgramName());
		log() << TestLog::Message
			  << "// Switched pipeline " << pipeline->getPipeline()
			  << "'s vertex stage to single-shader program " << vtxProg->getProgramName()
			  << TestLog::EndMessage;
	}
	if (m_params.switchFrg)
	{
		string newSource = genFrgShaderSrc(pp.frgSeed, m_varyings.frgInputs,
										   frgName, useUniform, pp.frgScale);
		frgProg = createSingleShaderProgram(glu::SHADERTYPE_FRAGMENT, newSource);
		pipeline->useProgramStages(GL_FRAGMENT_SHADER_BIT, frgProg->getProgramName());
		log() << TestLog::Message
			  << "// Switched pipeline " << pipeline->getPipeline()
			  << "'s fragment stage to single-shader program " << frgProg->getProgramName()
			  << TestLog::EndMessage;
	}

	if (m_params.switchVtx || m_params.switchFrg)
		m_status.check(pipeline->isValid(),
					   "Pipeline became invalid after changing a stage's program");

	if (m_params.useUniform)
	{
		ProgramWrapper& vtxStage = *(vtxProg ? vtxProg : fullProg);
		ProgramWrapper& frgStage = *(frgProg ? frgProg : fullProg);

		setUniform(vtxStage, vtxName, pp.vtxScale, m_params.useProgramUniform);
		setUniform(frgStage, frgName, pp.frgScale, m_params.useProgramUniform);
	}
	else
		log().writeMessage("// Programs use constants instead of uniforms");

	return MovePtr<Pipeline>(new Pipeline(pipeline, fullProg, vtxProg, frgProg));
}

MovePtr<ProgramWrapper> SeparateShaderTest::createReferenceProgram (const ProgramParams& pp)
{
	bool					useUniform	= m_params.useUniform;
	const string			vtxSrc		= genVtxShaderSrc(pp.vtxSeed,
														  varyingCompatVtxOutputs(m_varyings),
														  "vtxScale", useUniform, pp.vtxScale);
	const string			frgSrc		= genFrgShaderSrc(pp.frgSeed, m_varyings.frgInputs,
														  "frgScale", useUniform, pp.frgScale);
	MovePtr<ProgramWrapper>	program		= createShaderProgram(&vtxSrc, &frgSrc, false);
	GLuint					progName	= program->getProgramName();

	log() << TestLog::Message
		  << "// Created monolithic shader program " << progName
		  << TestLog::EndMessage;

	if (useUniform)
	{
		setUniform(*program, "vtxScale", pp.vtxScale, false);
		setUniform(*program, "frgScale", pp.frgScale, false);
	}

	return program;
}

void SeparateShaderTest::drawSurface (Surface& dst, deUint32 seed)
{
	const RenderContext&	renderCtx	= getRenderContext();
	Random					rnd			(seed > 0 ? seed : m_rnd.getUint32());
	Rectangle				viewport	= randomViewport(renderCtx, rnd,
														 VIEWPORT_SIZE, VIEWPORT_SIZE);
	glClearColor(0.125f, 0.25f, 0.5f, 1.f);
	setViewport(renderCtx, viewport);
	glClear(GL_COLOR_BUFFER_BIT);
	GLU_CHECK_CALL(glDrawArrays(GL_TRIANGLES, 0, 3));
	readRectangle(renderCtx, viewport, dst);
	log().writeMessage("// Drew a triangle");
}

void SeparateShaderTest::testPipelineRendering (MovePtr<Pipeline>& pipeOut)
{
	ProgramParams				pp			= genProgramParams(m_rnd);
	Pipeline&					pipeline	= *(pipeOut = createPipeline(pp));
	GLuint						pipeName	= pipeline.pipeline->getPipeline();
	UniquePtr<ProgramWrapper>	refProgram	(createReferenceProgram(pp));
	GLuint						refProgName	= refProgram->getProgramName();
	Surface						refSurface;
	Surface						pipelineSurface;
	GLuint						drawSeed	= m_rnd.getUint32();

	glUseProgram(refProgName);
	log() << TestLog::Message << "// Use program " << refProgName << TestLog::EndMessage;
	drawSurface(refSurface, drawSeed);
	glUseProgram(0);

	glBindProgramPipeline(pipeName);
	log() << TestLog::Message << "// Bind pipeline " << pipeName << TestLog::EndMessage;
	drawSurface(pipelineSurface, drawSeed);
	glBindProgramPipeline(0);

	{
		const bool result = tcu::fuzzyCompare(
			m_testCtx.getLog(), "Program pipeline result",
			"Result of comparing a program pipeline with a monolithic program",
			refSurface, pipelineSurface, 0.05f, tcu::COMPARE_LOG_RESULT);

		m_status.check(result, "Pipeline rendering differs from equivalent monolithic program");
	}
}

void SeparateShaderTest::testCurrentProgPriority (MovePtr<Pipeline>& pipeOut)
{
	ProgramParams				pipePp		= genProgramParams(m_rnd);
	ProgramParams				programPp	= genProgramParams(m_rnd);
	Pipeline&					pipeline	= *(pipeOut = createPipeline(pipePp));
	GLuint						pipeName	= pipeline.pipeline->getPipeline();
	UniquePtr<ProgramWrapper>	program		(createReferenceProgram(programPp));
	Surface						pipelineSurface;
	Surface						refSurface;
	Surface						resultSurface;
	deUint32					drawSeed	= m_rnd.getUint32();

	LOG_CALL(glBindProgramPipeline(pipeName));
	drawSurface(pipelineSurface, drawSeed);
	LOG_CALL(glBindProgramPipeline(0));

	LOG_CALL(glUseProgram(program->getProgramName()));
	drawSurface(refSurface, drawSeed);
	LOG_CALL(glUseProgram(0));

	LOG_CALL(glUseProgram(program->getProgramName()));
	LOG_CALL(glBindProgramPipeline(pipeName));
	drawSurface(resultSurface, drawSeed);
	LOG_CALL(glBindProgramPipeline(0));
	LOG_CALL(glUseProgram(0));

	bool result = tcu::pixelThresholdCompare(
		m_testCtx.getLog(), "Active program rendering result",
		"Active program rendering result",
		refSurface, resultSurface, tcu::RGBA(0, 0, 0, 0), tcu::COMPARE_LOG_RESULT);

	m_status.check(result, "glBindProgramPipeline() affects glUseProgram()");
	if (!result)
		log() << TestLog::Image("Pipeline image", "Image produced by pipeline",
								pipelineSurface);
}

void SeparateShaderTest::testActiveProgramUniform (MovePtr<Pipeline>& pipeOut)
{
	ProgramParams				refPp			= genProgramParams(m_rnd);
	Surface						refSurface;
	Surface						resultSurface;
	deUint32					drawSeed		= m_rnd.getUint32();

	DE_UNREF(pipeOut);
	{
		UniquePtr<ProgramWrapper>	refProg		(createReferenceProgram(refPp));
		GLuint						refProgName	= refProg->getProgramName();

		glUseProgram(refProgName);
		log() << TestLog::Message << "// Use reference program " << refProgName
			  << TestLog::EndMessage;
		drawSurface(refSurface, drawSeed);
		glUseProgram(0);
	}

	{
		ProgramParams				changePp	= genProgramParams(m_rnd);
		changePp.vtxSeed						= refPp.vtxSeed;
		changePp.frgSeed						= refPp.frgSeed;
		UniquePtr<ProgramWrapper>	changeProg	(createReferenceProgram(changePp));
		GLuint						changeName	= changeProg->getProgramName();
		ProgramPipeline				pipeline	(getRenderContext());
		GLint						vtxLoc		= glGetUniformLocation(changeName, "vtxScale");
		GLint						frgLoc		= glGetUniformLocation(changeName, "frgScale");

		LOG_CALL(glBindProgramPipeline(pipeline.getPipeline()));

		pipeline.activeShaderProgram(changeName);
		log() << TestLog::Message << "// Set active shader program to " << changeName
			  << TestLog::EndMessage;

		glUniform1f(vtxLoc, refPp.vtxScale);
		log() << TestLog::Message
			  << "// Set uniform 'vtxScale' to " << refPp.vtxScale << " using glUniform1f"
			  << TestLog::EndMessage;
		glUniform1f(frgLoc, refPp.frgScale);
		log() << TestLog::Message
			  << "// Set uniform 'frgScale' to " << refPp.frgScale << " using glUniform1f"
			  << TestLog::EndMessage;

		pipeline.activeShaderProgram(0);
		LOG_CALL(glBindProgramPipeline(0));

		LOG_CALL(glUseProgram(changeName));
		drawSurface(resultSurface, drawSeed);
		LOG_CALL(glUseProgram(0));
	}

	bool result = tcu::fuzzyCompare(
		m_testCtx.getLog(), "Active program uniform result",
		"Active program uniform result",
		refSurface, resultSurface, 0.05f, tcu::COMPARE_LOG_RESULT);

	m_status.check(result,
				   "glUniform() did not correctly modify "
				   "the active program of the bound pipeline");
}

void SeparateShaderTest::testPipelineQueryPrograms (MovePtr<Pipeline>& pipeOut)
{
	ProgramParams				pipePp		= genProgramParams(m_rnd);
	Pipeline&					pipeline	= *(pipeOut = createPipeline(pipePp));
	GLuint						pipeName	= pipeline.pipeline->getPipeline();
	GLint						queryVtx	= 0;
	GLint						queryFrg	= 0;

	LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_VERTEX_SHADER, &queryVtx)));
	m_status.check(GLuint(queryVtx) == pipeline.getVertexProgram().getProgramName(),
				   "Program pipeline query reported wrong vertex shader program");

	LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_FRAGMENT_SHADER, &queryFrg)));
	m_status.check(GLuint(queryFrg) == pipeline.getFragmentProgram().getProgramName(),
				   "Program pipeline query reported wrong fragment shader program");
}

void SeparateShaderTest::testPipelineQueryActive (MovePtr<Pipeline>& pipeOut)
{
	ProgramParams				pipePp		= genProgramParams(m_rnd);
	Pipeline&					pipeline	= *(pipeOut = createPipeline(pipePp));
	GLuint						pipeName	= pipeline.pipeline->getPipeline();
	GLuint						newActive	= pipeline.getVertexProgram().getProgramName();
	GLint						queryActive	= 0;

	LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_ACTIVE_PROGRAM, &queryActive)));
	m_status.check(queryActive == 0,
				   "Program pipeline query reported non-zero initial active program");

	pipeline.pipeline->activeShaderProgram(newActive);
	log() << TestLog::Message
		  << "Set pipeline " << pipeName << "'s active shader program to " << newActive
		  << TestLog::EndMessage;

	LOG_CALL(GLU_CHECK_CALL(glGetProgramPipelineiv(pipeName, GL_ACTIVE_PROGRAM, &queryActive)));
	m_status.check(GLuint(queryActive) == newActive,
				   "Program pipeline query reported incorrect active program");

	pipeline.pipeline->activeShaderProgram(0);
}

TestCase::IterateResult SeparateShaderTest::iterate (void)
{
	MovePtr<Pipeline> pipeline;

	DE_ASSERT(m_iterations > 0);

	if (m_currentIteration == 0)
		logParams(log(), m_params);

	++m_currentIteration;

	try
	{
		(this->*m_testFunc)(pipeline);
		log().writeMessage("");
	}
	catch (const tcu::Exception&)
	{
		if (pipeline)
			logPipeline(log(), *pipeline);
		throw;
	}

	if (m_status.getResult() != QP_TEST_RESULT_PASS)
	{
		if (pipeline)
			logPipeline(log(), *pipeline);
	}
	else if (m_currentIteration < m_iterations)
		return CONTINUE;

	m_status.setTestContextResult(m_testCtx);
	return STOP;
}

// Group construction utilities

enum ParamFlags
{
	PARAMFLAGS_SWITCH_FRAGMENT	= 1 << 0,
	PARAMFLAGS_SWITCH_VERTEX	= 1 << 1,
	PARAMFLAGS_INIT_SINGLE		= 1 << 2,
	PARAMFLAGS_LAST				= 1 << 3,
	PARAMFLAGS_MASK				= PARAMFLAGS_LAST - 1
};

bool areCaseParamFlagsValid (ParamFlags flags)
{
	const ParamFlags switchAll = ParamFlags(PARAMFLAGS_SWITCH_VERTEX|PARAMFLAGS_SWITCH_FRAGMENT);

	if ((flags & PARAMFLAGS_INIT_SINGLE) != 0)
		return (flags & switchAll) == 0 ||
			   (flags & switchAll) == switchAll;
	else
		return true;
}

bool addRenderTest (TestCaseGroup& group, const string& namePrefix, const string& descPrefix,
					int numIterations, ParamFlags flags, TestParams params)
{
	ostringstream	name;
	ostringstream	desc;

	DE_ASSERT(areCaseParamFlagsValid(flags));

	name << namePrefix;
	desc << descPrefix;

	params.initSingle	= (flags & PARAMFLAGS_INIT_SINGLE) != 0;
	params.switchVtx	= (flags & PARAMFLAGS_SWITCH_VERTEX) != 0;
	params.switchFrg	= (flags & PARAMFLAGS_SWITCH_FRAGMENT) != 0;

	name << (flags & PARAMFLAGS_INIT_SINGLE ? "single_program" : "separate_programs");
	desc << (flags & PARAMFLAGS_INIT_SINGLE
			 ? "Single program with two shaders"
			 : "Separate programs for each shader");

	switch (flags & (PARAMFLAGS_SWITCH_FRAGMENT | PARAMFLAGS_SWITCH_VERTEX))
	{
		case 0:
			break;
		case PARAMFLAGS_SWITCH_FRAGMENT:
			name << "_add_fragment";
			desc << ", then add a fragment program";
			break;
		case PARAMFLAGS_SWITCH_VERTEX:
			name << "_add_vertex";
			desc << ", then add a vertex program";
			break;
		case PARAMFLAGS_SWITCH_FRAGMENT | PARAMFLAGS_SWITCH_VERTEX:
			name << "_add_both";
			desc << ", then add both vertex and shader programs";
			break;
	}

	if (!paramsValid(params))
		return false;

	group.addChild(new SeparateShaderTest(group.getContext(), name.str(), desc.str(),
										  numIterations, params,
										  &SeparateShaderTest::testPipelineRendering));

	return true;
}

void describeInterpolation (const string& stage, VaryingInterpolation qual,
							ostringstream& name, ostringstream& desc)
{
	DE_ASSERT(qual < VARYINGINTERPOLATION_RANDOM);

	if (qual == VARYINGINTERPOLATION_DEFAULT)
	{
		desc << ", unqualified in " << stage << " shader";
		return;
	}
	else
	{
		const string qualName = glu::getInterpolationName(getGluInterpolation(qual));

		name << "_" << stage << "_" << qualName;
		desc << ", qualified '" << qualName << "' in " << stage << " shader";
	}
}


} // anonymous

TestCaseGroup* createSeparateShaderTests (Context& ctx)
{
	TestParams		defaultParams;
	int				numIterations	= 4;
	TestCaseGroup*	group			=
		new TestCaseGroup(ctx, "separate_shader", "Separate shader tests");

	defaultParams.useUniform			= false;
	defaultParams.initSingle			= false;
	defaultParams.switchVtx				= false;
	defaultParams.switchFrg				= false;
	defaultParams.useCreateHelper		= false;
	defaultParams.useProgramUniform		= false;
	defaultParams.useSameName			= false;
	defaultParams.varyings.count		= 0;
	defaultParams.varyings.type			= glu::TYPE_INVALID;
	defaultParams.varyings.binding		= BINDING_NAME;
	defaultParams.varyings.vtxInterp	= VARYINGINTERPOLATION_LAST;
	defaultParams.varyings.frgInterp	= VARYINGINTERPOLATION_LAST;

	TestCaseGroup* stagesGroup =
		new TestCaseGroup(ctx, "pipeline", "Pipeline configuration tests");
	group->addChild(stagesGroup);

	for (deUint32 flags = 0; flags < PARAMFLAGS_LAST << 2; ++flags)
	{
		TestParams		params			= defaultParams;
		ostringstream	name;
		ostringstream	desc;

		if (!areCaseParamFlagsValid(ParamFlags(flags & PARAMFLAGS_MASK)))
			continue;

		if (flags & (PARAMFLAGS_LAST << 1))
		{
			params.useSameName = true;
			name << "same_";
			desc << "Identically named ";
		}
		else
		{
			name << "different_";
			desc << "Differently named ";
		}

		if (flags & PARAMFLAGS_LAST)
		{
			params.useUniform = true;
			name << "uniform_";
			desc << "uniforms, ";
		}
		else
		{
			name << "constant_";
			desc << "constants, ";
		}

		addRenderTest(*stagesGroup, name.str(), desc.str(), numIterations,
					  ParamFlags(flags & PARAMFLAGS_MASK), params);
	}

	TestCaseGroup* programUniformGroup =
		new TestCaseGroup(ctx, "program_uniform", "ProgramUniform tests");
	group->addChild(programUniformGroup);

	for (deUint32 flags = 0; flags < PARAMFLAGS_LAST; ++flags)
	{
		TestParams		params			= defaultParams;

		if (!areCaseParamFlagsValid(ParamFlags(flags)))
			continue;

		params.useUniform = true;
		params.useProgramUniform = true;

		addRenderTest(*programUniformGroup, "", "", numIterations, ParamFlags(flags), params);
	}

	TestCaseGroup* createShaderProgramGroup =
		new TestCaseGroup(ctx, "create_shader_program", "CreateShaderProgram tests");
	group->addChild(createShaderProgramGroup);

	for (deUint32 flags = 0; flags < PARAMFLAGS_LAST; ++flags)
	{
		TestParams		params			= defaultParams;

		if (!areCaseParamFlagsValid(ParamFlags(flags)))
			continue;

		params.useCreateHelper = true;

		addRenderTest(*createShaderProgramGroup, "", "", numIterations,
					  ParamFlags(flags), params);
	}

	TestCaseGroup* interfaceGroup =
		new TestCaseGroup(ctx, "interface", "Shader interface compatibility tests");
	group->addChild(interfaceGroup);

	enum
	{
		NUM_INTERPOLATIONS	= VARYINGINTERPOLATION_RANDOM, // VARYINGINTERPOLATION_RANDOM is one after last fully specified interpolation
		INTERFACEFLAGS_LAST = BINDING_LAST * NUM_INTERPOLATIONS * NUM_INTERPOLATIONS
	};

	for (deUint32 flags = 0; flags < INTERFACEFLAGS_LAST; ++flags)
	{
		deUint32				tmpFlags	= flags;
		VaryingInterpolation	frgInterp	= VaryingInterpolation(tmpFlags % NUM_INTERPOLATIONS);
		VaryingInterpolation	vtxInterp	= VaryingInterpolation((tmpFlags /= NUM_INTERPOLATIONS)
																   % NUM_INTERPOLATIONS);
		BindingKind				binding		= BindingKind((tmpFlags /= NUM_INTERPOLATIONS)
														  % BINDING_LAST);
		TestParams				params		= defaultParams;
		ostringstream			name;
		ostringstream			desc;

		params.varyings.count		= 1;
		params.varyings.type		= glu::TYPE_FLOAT;
		params.varyings.binding		= binding;
		params.varyings.vtxInterp	= vtxInterp;
		params.varyings.frgInterp	= frgInterp;

		switch (binding)
		{
			case BINDING_LOCATION:
				name << "same_location";
				desc << "Varyings have same location, ";
				break;
			case BINDING_NAME:
				name << "same_name";
				desc << "Varyings have same name, ";
				break;
			default:
				DE_ASSERT(!"Impossible");
		}

		describeInterpolation("vertex", vtxInterp, name, desc);
		describeInterpolation("fragment", frgInterp, name, desc);

		if (!paramsValid(params))
			continue;

		interfaceGroup->addChild(
			new SeparateShaderTest(ctx, name.str(), desc.str(), numIterations, params,
								   &SeparateShaderTest::testPipelineRendering));
	}

	deUint32		baseSeed	= ctx.getTestContext().getCommandLine().getBaseSeed();
	Random			rnd			(deStringHash("separate_shader.random") + baseSeed);
	set<string>		seen;
	TestCaseGroup*	randomGroup	= new TestCaseGroup(
		ctx, "random", "Random pipeline configuration tests");
	group->addChild(randomGroup);

	for (deUint32 i = 0; i < 128; ++i)
	{
		TestParams		params;
		string			code;
		deUint32		genIterations	= 4096;

		do
		{
			params	= genParams(rnd.getUint32());
			code	= paramsCode(params);
		} while (de::contains(seen, code) && --genIterations > 0);

		seen.insert(code);

		string name = de::toString(i); // Would be code but baseSeed can change

		randomGroup->addChild(new SeparateShaderTest(
								  ctx, name, name, numIterations, params,
								  &SeparateShaderTest::testPipelineRendering));
	}

	TestCaseGroup* apiGroup =
		new TestCaseGroup(ctx, "api", "Program pipeline API tests");
	group->addChild(apiGroup);

	{
		// More or less random parameters. These shouldn't have much effect, so just
		// do a single sample.
		TestParams params = defaultParams;
		params.useUniform = true;
		apiGroup->addChild(new SeparateShaderTest(
								  ctx,
								  "current_program_priority",
								  "Test priority between current program and pipeline binding",
								  1, params, &SeparateShaderTest::testCurrentProgPriority));
		apiGroup->addChild(new SeparateShaderTest(
								  ctx,
								  "active_program_uniform",
								  "Test that glUniform() affects a pipeline's active program",
								  1, params, &SeparateShaderTest::testActiveProgramUniform));

		apiGroup->addChild(new SeparateShaderTest(
								 ctx,
								 "pipeline_programs",
								 "Test queries for programs in program pipeline stages",
								 1, params, &SeparateShaderTest::testPipelineQueryPrograms));

		apiGroup->addChild(new SeparateShaderTest(
								 ctx,
								 "pipeline_active",
								 "Test query for active programs in a program pipeline",
								 1, params, &SeparateShaderTest::testPipelineQueryActive));
	}

	TestCaseGroup* interfaceMismatchGroup =
		new TestCaseGroup(ctx, "validation", "Negative program pipeline interface matching");
	group->addChild(interfaceMismatchGroup);

	{
		gls::ShaderLibrary					shaderLibrary	(ctx.getTestContext(), ctx.getRenderContext(), ctx.getContextInfo());
		const std::vector<tcu::TestNode*>	children		= shaderLibrary.loadShaderFile("shaders/separate_shader_validation.test");

		for (int i = 0; i < (int)children.size(); i++)
			interfaceMismatchGroup->addChild(children[i]);
	}

	return group;
}

} // Functional
} // gles31
} // deqp