/*-------------------------------------------------------------------------
 * OpenGL Conformance Test Suite
 * -----------------------------
 *
 * Copyright (c) 2016 Google Inc.
 * Copyright (c) 2016 The Khronos Group Inc.
 *
 * 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 Compiler test case.
 */ /*-------------------------------------------------------------------*/

#include "glcShaderLibraryCase.hpp"

#include "tcuRenderTarget.hpp"
#include "tcuTestLog.hpp"

#include "gluDrawUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluShaderProgram.hpp"
#include "tcuStringTemplate.hpp"

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

#include "deInt32.h"
#include "deMath.h"
#include "deRandom.hpp"
#include "deString.h"

#include <map>
#include <sstream>
#include <string>
#include <vector>

using namespace std;
using namespace tcu;
using namespace glu;

namespace deqp
{
namespace sl
{

enum
{
	VIEWPORT_WIDTH  = 128,
	VIEWPORT_HEIGHT = 128
};

static inline bool usesShaderInoutQualifiers(glu::GLSLVersion version)
{
	switch (version)
	{
	case glu::GLSL_VERSION_100_ES:
	case glu::GLSL_VERSION_130:
	case glu::GLSL_VERSION_140:
	case glu::GLSL_VERSION_150:
		return false;

	default:
		return true;
	}
}

// ShaderCase.

ShaderCase::ShaderCase(tcu::TestContext& testCtx, RenderContext& renderCtx, const char* name, const char* description,
					   ExpectResult expectResult, const std::vector<ValueBlock>& valueBlocks, GLSLVersion targetVersion,
					   const char* vertexSource, const char* fragmentSource)
	: tcu::TestCase(testCtx, name, description)
	, m_renderCtx(renderCtx)
	, m_expectResult(expectResult)
	, m_valueBlocks(valueBlocks)
	, m_targetVersion(targetVersion)
{
	// If no value blocks given, use an empty one.
	if (m_valueBlocks.size() == 0)
		m_valueBlocks.push_back(ValueBlock());

	// Use first value block to specialize shaders.
	const ValueBlock& valueBlock = m_valueBlocks[0];

	// \todo [2010-04-01 petri] Check that all value blocks have matching values.

	// Generate specialized shader sources.
	if (vertexSource && fragmentSource)
	{
		m_caseType = CASETYPE_COMPLETE;
		specializeShaders(vertexSource, fragmentSource, m_vertexSource, m_fragmentSource, valueBlock);
	}
	else if (vertexSource)
	{
		m_caseType		 = CASETYPE_VERTEX_ONLY;
		m_vertexSource   = specializeVertexShader(vertexSource, valueBlock);
		m_fragmentSource = genFragmentShader(valueBlock);
	}
	else
	{
		DE_ASSERT(fragmentSource);
		m_caseType		 = CASETYPE_FRAGMENT_ONLY;
		m_vertexSource   = genVertexShader(valueBlock);
		m_fragmentSource = specializeFragmentShader(fragmentSource, valueBlock);
	}
}

ShaderCase::~ShaderCase(void)
{
}

static void setUniformValue(const glw::Functions& gl, deUint32 programID, const std::string& name,
							const ShaderCase::Value& val, int arrayNdx)
{
	int scalarSize = getDataTypeScalarSize(val.dataType);
	int loc		   = gl.getUniformLocation(programID, name.c_str());

	TCU_CHECK_MSG(loc != -1, "uniform location not found");

	DE_STATIC_ASSERT(sizeof(ShaderCase::Value::Element) == sizeof(glw::GLfloat));
	DE_STATIC_ASSERT(sizeof(ShaderCase::Value::Element) == sizeof(glw::GLint));

	int elemNdx = (val.arrayLength == 1) ? 0 : (arrayNdx * scalarSize);

	switch (val.dataType)
	{
	case TYPE_FLOAT:
		gl.uniform1fv(loc, 1, &val.elements[elemNdx].float32);
		break;
	case TYPE_FLOAT_VEC2:
		gl.uniform2fv(loc, 1, &val.elements[elemNdx].float32);
		break;
	case TYPE_FLOAT_VEC3:
		gl.uniform3fv(loc, 1, &val.elements[elemNdx].float32);
		break;
	case TYPE_FLOAT_VEC4:
		gl.uniform4fv(loc, 1, &val.elements[elemNdx].float32);
		break;
	case TYPE_FLOAT_MAT2:
		gl.uniformMatrix2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);
		break;
	case TYPE_FLOAT_MAT3:
		gl.uniformMatrix3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);
		break;
	case TYPE_FLOAT_MAT4:
		gl.uniformMatrix4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);
		break;
	case TYPE_INT:
		gl.uniform1iv(loc, 1, &val.elements[elemNdx].int32);
		break;
	case TYPE_INT_VEC2:
		gl.uniform2iv(loc, 1, &val.elements[elemNdx].int32);
		break;
	case TYPE_INT_VEC3:
		gl.uniform3iv(loc, 1, &val.elements[elemNdx].int32);
		break;
	case TYPE_INT_VEC4:
		gl.uniform4iv(loc, 1, &val.elements[elemNdx].int32);
		break;
	case TYPE_BOOL:
		gl.uniform1iv(loc, 1, &val.elements[elemNdx].int32);
		break;
	case TYPE_BOOL_VEC2:
		gl.uniform2iv(loc, 1, &val.elements[elemNdx].int32);
		break;
	case TYPE_BOOL_VEC3:
		gl.uniform3iv(loc, 1, &val.elements[elemNdx].int32);
		break;
	case TYPE_BOOL_VEC4:
		gl.uniform4iv(loc, 1, &val.elements[elemNdx].int32);
		break;
	case TYPE_UINT:
		gl.uniform1uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);
		break;
	case TYPE_UINT_VEC2:
		gl.uniform2uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);
		break;
	case TYPE_UINT_VEC3:
		gl.uniform3uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);
		break;
	case TYPE_UINT_VEC4:
		gl.uniform4uiv(loc, 1, (const deUint32*)&val.elements[elemNdx].int32);
		break;
	case TYPE_FLOAT_MAT2X3:
		gl.uniformMatrix2x3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);
		break;
	case TYPE_FLOAT_MAT2X4:
		gl.uniformMatrix2x4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);
		break;
	case TYPE_FLOAT_MAT3X2:
		gl.uniformMatrix3x2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);
		break;
	case TYPE_FLOAT_MAT3X4:
		gl.uniformMatrix3x4fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);
		break;
	case TYPE_FLOAT_MAT4X2:
		gl.uniformMatrix4x2fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);
		break;
	case TYPE_FLOAT_MAT4X3:
		gl.uniformMatrix4x3fv(loc, 1, GL_FALSE, &val.elements[elemNdx].float32);
		break;

	case TYPE_SAMPLER_2D:
	case TYPE_SAMPLER_CUBE:
		DE_ASSERT(DE_FALSE && "implement!");
		break;

	default:
		DE_ASSERT(false);
	}
}

bool ShaderCase::checkPixels(Surface& surface, int minX, int maxX, int minY, int maxY)
{
	TestLog& log		   = m_testCtx.getLog();
	bool	 allWhite	  = true;
	bool	 allBlack	  = true;
	bool	 anyUnexpected = false;

	DE_ASSERT((maxX > minX) && (maxY > minY));

	for (int y = minY; y <= maxY; y++)
	{
		for (int x = minX; x <= maxX; x++)
		{
			RGBA pixel = surface.getPixel(x, y);
			// Note: we really do not want to involve alpha in the check comparison
			// \todo [2010-09-22 kalle] Do we know that alpha would be one? If yes, could use color constants white and black.
			bool isWhite = (pixel.getRed() == 255) && (pixel.getGreen() == 255) && (pixel.getBlue() == 255);
			bool isBlack = (pixel.getRed() == 0) && (pixel.getGreen() == 0) && (pixel.getBlue() == 0);

			allWhite	  = allWhite && isWhite;
			allBlack	  = allBlack && isBlack;
			anyUnexpected = anyUnexpected || (!isWhite && !isBlack);
		}
	}

	if (!allWhite)
	{
		if (anyUnexpected)
			log << TestLog::Message
				<< "WARNING: expecting all rendered pixels to be white or black, but got other colors as well!"
				<< TestLog::EndMessage;
		else if (!allBlack)
			log << TestLog::Message
				<< "WARNING: got inconsistent results over the image, when all pixels should be the same color!"
				<< TestLog::EndMessage;

		return false;
	}
	return true;
}

bool ShaderCase::execute(void)
{
	TestLog&			  log = m_testCtx.getLog();
	const glw::Functions& gl  = m_renderCtx.getFunctions();

	// Compute viewport.
	const tcu::RenderTarget& renderTarget = m_renderCtx.getRenderTarget();
	de::Random				 rnd(deStringHash(getName()));
	int						 width				= deMin32(renderTarget.getWidth(), VIEWPORT_WIDTH);
	int						 height				= deMin32(renderTarget.getHeight(), VIEWPORT_HEIGHT);
	int						 viewportX			= rnd.getInt(0, renderTarget.getWidth() - width);
	int						 viewportY			= rnd.getInt(0, renderTarget.getHeight() - height);
	const int				 numVerticesPerDraw = 4;

	GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderCase::execute(): start");

	// Setup viewport.
	gl.viewport(viewportX, viewportY, width, height);

	const float		   quadSize			  = 1.0f;
	static const float s_positions[4 * 4] = { -quadSize, -quadSize, 0.0f, 1.0f, -quadSize, +quadSize, 0.0f, 1.0f,
											  +quadSize, -quadSize, 0.0f, 1.0f, +quadSize, +quadSize, 0.0f, 1.0f };

	static const deUint16 s_indices[2 * 3] = { 0, 1, 2, 1, 3, 2 };

	// Setup program.
	glu::ShaderProgram program(m_renderCtx, glu::makeVtxFragSources(m_vertexSource.c_str(), m_fragmentSource.c_str()));

	// Check that compile/link results are what we expect.
	bool		vertexOk   = program.getShaderInfo(SHADERTYPE_VERTEX).compileOk;
	bool		fragmentOk = program.getShaderInfo(SHADERTYPE_FRAGMENT).compileOk;
	bool		linkOk	 = program.getProgramInfo().linkOk;
	const char* failReason = DE_NULL;

	log << program;

	switch (m_expectResult)
	{
	case EXPECT_PASS:
		if (!vertexOk || !fragmentOk)
			failReason = "expected shaders to compile and link properly, but failed to compile.";
		else if (!linkOk)
			failReason = "expected shaders to compile and link properly, but failed to link.";
		break;

	case EXPECT_COMPILE_FAIL:
		if (vertexOk && fragmentOk && !linkOk)
			failReason = "expected compilation to fail, but both shaders compiled and link failed.";
		else if (vertexOk && fragmentOk)
			failReason = "expected compilation to fail, but both shaders compiled correctly.";
		break;

	case EXPECT_LINK_FAIL:
		if (!vertexOk || !fragmentOk)
			failReason = "expected linking to fail, but unable to compile.";
		else if (linkOk)
			failReason = "expected linking to fail, but passed.";
		break;

	default:
		DE_ASSERT(false);
		return false;
	}

	if (failReason != DE_NULL)
	{
		// \todo [2010-06-07 petri] These should be handled in the test case?
		log << TestLog::Message << "ERROR: " << failReason << TestLog::EndMessage;

		// If implementation parses shader at link time, report it as quality warning.
		if (m_expectResult == EXPECT_COMPILE_FAIL && vertexOk && fragmentOk && !linkOk)
			m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, failReason);
		else
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, failReason);
		return false;
	}

	// Return if compile/link expected to fail.
	if (m_expectResult != EXPECT_PASS)
		return (failReason == DE_NULL);

	// Start using program.
	deUint32 programID = program.getProgram();
	gl.useProgram(programID);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glUseProgram()");

	// Fetch location for positions positions.
	int positionLoc = gl.getAttribLocation(programID, "dEQP_Position");
	if (positionLoc == -1)
	{
		string errStr = string("no location found for attribute 'dEQP_Position'");
		TCU_FAIL(errStr.c_str());
	}

	// Iterate all value blocks.
	for (int blockNdx = 0; blockNdx < (int)m_valueBlocks.size(); blockNdx++)
	{
		const ValueBlock& valueBlock = m_valueBlocks[blockNdx];

		// Iterate all array sub-cases.
		for (int arrayNdx = 0; arrayNdx < valueBlock.arrayLength; arrayNdx++)
		{
			int						   numValues = (int)valueBlock.values.size();
			vector<VertexArrayBinding> vertexArrays;

			int					   attribValueNdx = 0;
			vector<vector<float> > attribValues(numValues);

			vertexArrays.push_back(va::Float(positionLoc, 4, numVerticesPerDraw, 0, &s_positions[0]));

			// Collect VA pointer for inputs and set uniform values for outputs (refs).
			for (int valNdx = 0; valNdx < numValues; valNdx++)
			{
				const ShaderCase::Value& val		= valueBlock.values[valNdx];
				const char*				 valueName  = val.valueName.c_str();
				DataType				 dataType   = val.dataType;
				int						 scalarSize = getDataTypeScalarSize(val.dataType);

				GLU_EXPECT_NO_ERROR(gl.getError(), "before set uniforms");

				if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
				{
					// Replicate values four times.
					std::vector<float>& scalars = attribValues[attribValueNdx++];
					scalars.resize(numVerticesPerDraw * scalarSize);
					if (isDataTypeFloatOrVec(dataType) || isDataTypeMatrix(dataType))
					{
						for (int repNdx = 0; repNdx < numVerticesPerDraw; repNdx++)
							for (int ndx						   = 0; ndx < scalarSize; ndx++)
								scalars[repNdx * scalarSize + ndx] = val.elements[arrayNdx * scalarSize + ndx].float32;
					}
					else
					{
						// convert to floats.
						for (int repNdx = 0; repNdx < numVerticesPerDraw; repNdx++)
						{
							for (int ndx = 0; ndx < scalarSize; ndx++)
							{
								float v = (float)val.elements[arrayNdx * scalarSize + ndx].int32;
								DE_ASSERT(val.elements[arrayNdx * scalarSize + ndx].int32 == (int)v);
								scalars[repNdx * scalarSize + ndx] = v;
							}
						}
					}

					// Attribute name prefix.
					string attribPrefix = "";
					// \todo [2010-05-27 petri] Should latter condition only apply for vertex cases (or actually non-fragment cases)?
					if ((m_caseType == CASETYPE_FRAGMENT_ONLY) || (getDataTypeScalarType(dataType) != TYPE_FLOAT))
						attribPrefix = "a_";

					// Input always given as attribute.
					string attribName = attribPrefix + valueName;
					int	attribLoc  = gl.getAttribLocation(programID, attribName.c_str());
					if (attribLoc == -1)
					{
						log << TestLog::Message << "Warning: no location found for attribute '" << attribName << "'"
							<< TestLog::EndMessage;
						continue;
					}

					if (isDataTypeMatrix(dataType))
					{
						int numCols = getDataTypeMatrixNumColumns(dataType);
						int numRows = getDataTypeMatrixNumRows(dataType);
						DE_ASSERT(scalarSize == numCols * numRows);

						for (int i = 0; i < numCols; i++)
							vertexArrays.push_back(va::Float(attribLoc + i, numRows, numVerticesPerDraw,
															 static_cast<int>(scalarSize * sizeof(float)),
															 &scalars[i * numRows]));
					}
					else
					{
						DE_ASSERT(isDataTypeFloatOrVec(dataType) || isDataTypeIntOrIVec(dataType) ||
								  isDataTypeUintOrUVec(dataType) || isDataTypeBoolOrBVec(dataType));
						vertexArrays.push_back(va::Float(attribLoc, scalarSize, numVerticesPerDraw, 0, &scalars[0]));
					}

					GLU_EXPECT_NO_ERROR(gl.getError(), "set vertex attrib array");
				}
				else if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
				{
					// Set reference value.
					string refName = string("ref_") + valueName;
					setUniformValue(gl, programID, refName, val, arrayNdx);
					GLU_EXPECT_NO_ERROR(gl.getError(), "set reference uniforms");
				}
				else
				{
					DE_ASSERT(val.storageType == ShaderCase::Value::STORAGE_UNIFORM);
					setUniformValue(gl, programID, valueName, val, arrayNdx);
					GLU_EXPECT_NO_ERROR(gl.getError(), "set uniforms");
				}
			}

			// Clear.
			gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
			gl.clear(GL_COLOR_BUFFER_BIT);
			GLU_EXPECT_NO_ERROR(gl.getError(), "clear buffer");

			// Draw.
			draw(m_renderCtx, program.getProgram(), (int)vertexArrays.size(), &vertexArrays[0],
				 pr::Triangles(DE_LENGTH_OF_ARRAY(s_indices), &s_indices[0]));
			GLU_EXPECT_NO_ERROR(gl.getError(), "draw");

			// Read back results.
			Surface surface(width, height);
			glu::readPixels(m_renderCtx, viewportX, viewportY, surface.getAccess());
			GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");

			float w	= s_positions[3];
			int   minY = deCeilFloatToInt32(((-quadSize / w) * 0.5f + 0.5f) * (float)height + 1.0f);
			int   maxY = deFloorFloatToInt32(((+quadSize / w) * 0.5f + 0.5f) * (float)height - 0.5f);
			int   minX = deCeilFloatToInt32(((-quadSize / w) * 0.5f + 0.5f) * (float)width + 1.0f);
			int   maxX = deFloorFloatToInt32(((+quadSize / w) * 0.5f + 0.5f) * (float)width - 0.5f);

			if (!checkPixels(surface, minX, maxX, minY, maxY))
			{
				log << TestLog::Message << "INCORRECT RESULT for (value block " << (blockNdx + 1) << " of "
					<< (int)m_valueBlocks.size() << ", sub-case " << arrayNdx + 1 << " of " << valueBlock.arrayLength
					<< "):" << TestLog::EndMessage;

				log << TestLog::Message << "Failing shader input/output values:" << TestLog::EndMessage;
				dumpValues(valueBlock, arrayNdx);

				// Dump image on failure.
				log << TestLog::Image("Result", "Rendered result image", surface);

				gl.useProgram(0);
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
				return false;
			}
		}
	}

	gl.useProgram(0);
	GLU_EXPECT_NO_ERROR(gl.getError(), "ShaderCase::execute(): end");
	return true;
}

TestCase::IterateResult ShaderCase::iterate(void)
{
	// Initialize state to pass.
	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	bool executeOk = execute();

	DE_ASSERT(executeOk ? m_testCtx.getTestResult() == QP_TEST_RESULT_PASS :
						  m_testCtx.getTestResult() != QP_TEST_RESULT_PASS);
	(void)executeOk;
	return TestCase::STOP;
}

// This functions builds a matching vertex shader for a 'both' case, when
// the fragment shader is being tested.
// We need to build attributes and varyings for each 'input'.
string ShaderCase::genVertexShader(const ValueBlock& valueBlock)
{
	ostringstream res;
	const bool	usesInout = usesShaderInoutQualifiers(m_targetVersion);
	const char*   vtxIn		= usesInout ? "in" : "attribute";
	const char*   vtxOut	= usesInout ? "out" : "varying";

	res << glu::getGLSLVersionDeclaration(m_targetVersion) << "\n";

	// Declarations (position + attribute/varying for each input).
	res << "precision highp float;\n";
	res << "precision highp int;\n";
	res << "\n";
	res << vtxIn << " highp vec4 dEQP_Position;\n";
	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
	{
		const ShaderCase::Value& val = valueBlock.values[ndx];
		if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
		{
			DataType	floatType = getDataTypeFloatScalars(val.dataType);
			const char* typeStr   = getDataTypeName(floatType);
			res << vtxIn << " " << typeStr << " a_" << val.valueName << ";\n";

			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
				res << vtxOut << " " << typeStr << " " << val.valueName << ";\n";
			else
				res << vtxOut << " " << typeStr << " v_" << val.valueName << ";\n";
		}
	}
	res << "\n";

	// Main function.
	// - gl_Position = dEQP_Position;
	// - for each input: write attribute directly to varying
	res << "void main()\n";
	res << "{\n";
	res << "    gl_Position = dEQP_Position;\n";
	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
	{
		const ShaderCase::Value& val = valueBlock.values[ndx];
		if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
		{
			const string& name = val.valueName;
			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
				res << "    " << name << " = a_" << name << ";\n";
			else
				res << "    v_" << name << " = a_" << name << ";\n";
		}
	}

	res << "}\n";
	return res.str();
}

static void genCompareFunctions(ostringstream& stream, const ShaderCase::ValueBlock& valueBlock, bool useFloatTypes)
{
	bool cmpTypeFound[TYPE_LAST];
	for (int i			= 0; i < TYPE_LAST; i++)
		cmpTypeFound[i] = false;

	for (int valueNdx = 0; valueNdx < (int)valueBlock.values.size(); valueNdx++)
	{
		const ShaderCase::Value& val = valueBlock.values[valueNdx];
		if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
			cmpTypeFound[(int)val.dataType] = true;
	}

	if (useFloatTypes)
	{
		if (cmpTypeFound[TYPE_BOOL])
			stream << "bool isOk (float a, bool b) { return ((a > 0.5) == b); }\n";
		if (cmpTypeFound[TYPE_BOOL_VEC2])
			stream << "bool isOk (vec2 a, bvec2 b) { return (greaterThan(a, vec2(0.5)) == b); }\n";
		if (cmpTypeFound[TYPE_BOOL_VEC3])
			stream << "bool isOk (vec3 a, bvec3 b) { return (greaterThan(a, vec3(0.5)) == b); }\n";
		if (cmpTypeFound[TYPE_BOOL_VEC4])
			stream << "bool isOk (vec4 a, bvec4 b) { return (greaterThan(a, vec4(0.5)) == b); }\n";
		if (cmpTypeFound[TYPE_INT])
			stream << "bool isOk (float a, int b)  { float atemp = a+0.5; return (float(b) <= atemp && atemp <= "
					  "float(b+1)); }\n";
		if (cmpTypeFound[TYPE_INT_VEC2])
			stream << "bool isOk (vec2 a, ivec2 b) { return (ivec2(floor(a + 0.5)) == b); }\n";
		if (cmpTypeFound[TYPE_INT_VEC3])
			stream << "bool isOk (vec3 a, ivec3 b) { return (ivec3(floor(a + 0.5)) == b); }\n";
		if (cmpTypeFound[TYPE_INT_VEC4])
			stream << "bool isOk (vec4 a, ivec4 b) { return (ivec4(floor(a + 0.5)) == b); }\n";
		if (cmpTypeFound[TYPE_UINT])
			stream << "bool isOk (float a, uint b) { float atemp = a+0.5; return (float(b) <= atemp && atemp <= "
					  "float(b+1)); }\n";
		if (cmpTypeFound[TYPE_UINT_VEC2])
			stream << "bool isOk (vec2 a, uvec2 b) { return (uvec2(floor(a + 0.5)) == b); }\n";
		if (cmpTypeFound[TYPE_UINT_VEC3])
			stream << "bool isOk (vec3 a, uvec3 b) { return (uvec3(floor(a + 0.5)) == b); }\n";
		if (cmpTypeFound[TYPE_UINT_VEC4])
			stream << "bool isOk (vec4 a, uvec4 b) { return (uvec4(floor(a + 0.5)) == b); }\n";
	}
	else
	{
		if (cmpTypeFound[TYPE_BOOL])
			stream << "bool isOk (bool a, bool b)   { return (a == b); }\n";
		if (cmpTypeFound[TYPE_BOOL_VEC2])
			stream << "bool isOk (bvec2 a, bvec2 b) { return (a == b); }\n";
		if (cmpTypeFound[TYPE_BOOL_VEC3])
			stream << "bool isOk (bvec3 a, bvec3 b) { return (a == b); }\n";
		if (cmpTypeFound[TYPE_BOOL_VEC4])
			stream << "bool isOk (bvec4 a, bvec4 b) { return (a == b); }\n";
		if (cmpTypeFound[TYPE_INT])
			stream << "bool isOk (int a, int b)     { return (a == b); }\n";
		if (cmpTypeFound[TYPE_INT_VEC2])
			stream << "bool isOk (ivec2 a, ivec2 b) { return (a == b); }\n";
		if (cmpTypeFound[TYPE_INT_VEC3])
			stream << "bool isOk (ivec3 a, ivec3 b) { return (a == b); }\n";
		if (cmpTypeFound[TYPE_INT_VEC4])
			stream << "bool isOk (ivec4 a, ivec4 b) { return (a == b); }\n";
		if (cmpTypeFound[TYPE_UINT])
			stream << "bool isOk (uint a, uint b)   { return (a == b); }\n";
		if (cmpTypeFound[TYPE_UINT_VEC2])
			stream << "bool isOk (uvec2 a, uvec2 b) { return (a == b); }\n";
		if (cmpTypeFound[TYPE_UINT_VEC3])
			stream << "bool isOk (uvec3 a, uvec3 b) { return (a == b); }\n";
		if (cmpTypeFound[TYPE_UINT_VEC4])
			stream << "bool isOk (uvec4 a, uvec4 b) { return (a == b); }\n";
	}

	if (cmpTypeFound[TYPE_FLOAT])
		stream << "bool isOk (float a, float b, float eps) { return (abs(a-b) <= (eps*abs(b) + eps)); }\n";
	if (cmpTypeFound[TYPE_FLOAT_VEC2])
		stream
			<< "bool isOk (vec2 a, vec2 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n";
	if (cmpTypeFound[TYPE_FLOAT_VEC3])
		stream
			<< "bool isOk (vec3 a, vec3 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n";
	if (cmpTypeFound[TYPE_FLOAT_VEC4])
		stream
			<< "bool isOk (vec4 a, vec4 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n";

	if (cmpTypeFound[TYPE_FLOAT_MAT2])
		stream << "bool isOk (mat2 a, mat2 b, float eps) { vec2 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return "
				  "all(lessThanEqual(diff, vec2(eps))); }\n";
	if (cmpTypeFound[TYPE_FLOAT_MAT2X3])
		stream << "bool isOk (mat2x3 a, mat2x3 b, float eps) { vec3 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return "
				  "all(lessThanEqual(diff, vec3(eps))); }\n";
	if (cmpTypeFound[TYPE_FLOAT_MAT2X4])
		stream << "bool isOk (mat2x4 a, mat2x4 b, float eps) { vec4 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return "
				  "all(lessThanEqual(diff, vec4(eps))); }\n";
	if (cmpTypeFound[TYPE_FLOAT_MAT3X2])
		stream << "bool isOk (mat3x2 a, mat3x2 b, float eps) { vec2 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
				  "abs(a[2]-b[2])); return all(lessThanEqual(diff, vec2(eps))); }\n";
	if (cmpTypeFound[TYPE_FLOAT_MAT3])
		stream << "bool isOk (mat3 a, mat3 b, float eps) { vec3 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
				  "abs(a[2]-b[2])); return all(lessThanEqual(diff, vec3(eps))); }\n";
	if (cmpTypeFound[TYPE_FLOAT_MAT3X4])
		stream << "bool isOk (mat3x4 a, mat3x4 b, float eps) { vec4 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
				  "abs(a[2]-b[2])); return all(lessThanEqual(diff, vec4(eps))); }\n";
	if (cmpTypeFound[TYPE_FLOAT_MAT4X2])
		stream << "bool isOk (mat4x2 a, mat4x2 b, float eps) { vec2 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
				  "max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec2(eps))); }\n";
	if (cmpTypeFound[TYPE_FLOAT_MAT4X3])
		stream << "bool isOk (mat4x3 a, mat4x3 b, float eps) { vec3 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
				  "max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec3(eps))); }\n";
	if (cmpTypeFound[TYPE_FLOAT_MAT4])
		stream << "bool isOk (mat4 a, mat4 b, float eps) { vec4 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), "
				  "max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec4(eps))); }\n";
}

static void genCompareOp(ostringstream& output, const char* dstVec4Var, const ShaderCase::ValueBlock& valueBlock,
						 const char* nonFloatNamePrefix, const char* checkVarName)
{
	bool isFirstOutput = true;

	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
	{
		const ShaderCase::Value& val	   = valueBlock.values[ndx];
		const char*				 valueName = val.valueName.c_str();

		if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
		{
			// Check if we're only interested in one variable (then skip if not the right one).
			if (checkVarName && !deStringEqual(valueName, checkVarName))
				continue;

			// Prefix.
			if (isFirstOutput)
			{
				output << "bool RES = ";
				isFirstOutput = false;
			}
			else
				output << "RES = RES && ";

			// Generate actual comparison.
			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
				output << "isOk(" << valueName << ", ref_" << valueName << ", 0.05);\n";
			else
				output << "isOk(" << nonFloatNamePrefix << valueName << ", ref_" << valueName << ");\n";
		}
		// \note Uniforms are already declared in shader.
	}

	if (isFirstOutput)
		output << dstVec4Var << " = vec4(1.0);\n"; // \todo [petri] Should we give warning if not expect-failure case?
	else
		output << dstVec4Var << " = vec4(RES, RES, RES, 1.0);\n";
}

string ShaderCase::genFragmentShader(const ValueBlock& valueBlock)
{
	ostringstream shader;
	const bool	usesInout		 = usesShaderInoutQualifiers(m_targetVersion);
	const bool	customColorOut = usesInout;
	const char*   fragIn		 = usesInout ? "in" : "varying";

	shader << glu::getGLSLVersionDeclaration(m_targetVersion) << "\n";

	shader << "precision mediump float;\n";
	shader << "precision mediump int;\n";
	shader << "\n";

	if (customColorOut)
	{
		shader << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";
		shader << "\n";
	}

	genCompareFunctions(shader, valueBlock, true);
	shader << "\n";

	// Declarations (varying, reference for each output).
	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
	{
		const ShaderCase::Value& val		  = valueBlock.values[ndx];
		DataType				 floatType	= getDataTypeFloatScalars(val.dataType);
		const char*				 floatTypeStr = getDataTypeName(floatType);
		const char*				 refTypeStr   = getDataTypeName(val.dataType);

		if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
		{
			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
				shader << fragIn << " " << floatTypeStr << " " << val.valueName << ";\n";
			else
				shader << fragIn << " " << floatTypeStr << " v_" << val.valueName << ";\n";

			shader << "uniform " << refTypeStr << " ref_" << val.valueName << ";\n";
		}
	}

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

	shader << " ";
	genCompareOp(shader, customColorOut ? "dEQP_FragColor" : "gl_FragColor", valueBlock, "v_", DE_NULL);

	shader << "}\n";
	return shader.str();
}

// Specialize a shader for the vertex shader test case.
string ShaderCase::specializeVertexShader(const char* src, const ValueBlock& valueBlock)
{
	ostringstream decl;
	ostringstream setup;
	ostringstream output;
	const bool	usesInout = usesShaderInoutQualifiers(m_targetVersion);
	const char*   vtxIn		= usesInout ? "in" : "attribute";
	const char*   vtxOut	= usesInout ? "out" : "varying";

	// Output (write out position).
	output << "gl_Position = dEQP_Position;\n";

	// Declarations (position + attribute for each input, varying for each output).
	decl << vtxIn << " highp vec4 dEQP_Position;\n";
	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
	{
		const ShaderCase::Value& val		  = valueBlock.values[ndx];
		const char*				 valueName	= val.valueName.c_str();
		DataType				 floatType	= getDataTypeFloatScalars(val.dataType);
		const char*				 floatTypeStr = getDataTypeName(floatType);
		const char*				 refTypeStr   = getDataTypeName(val.dataType);

		if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
		{
			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
			{
				decl << vtxIn << " " << floatTypeStr << " " << valueName << ";\n";
			}
			else
			{
				decl << vtxIn << " " << floatTypeStr << " a_" << valueName << ";\n";
				setup << refTypeStr << " " << valueName << " = " << refTypeStr << "(a_" << valueName << ");\n";
			}
		}
		else if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
		{
			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
				decl << vtxOut << " " << floatTypeStr << " " << valueName << ";\n";
			else
			{
				decl << vtxOut << " " << floatTypeStr << " v_" << valueName << ";\n";
				decl << refTypeStr << " " << valueName << ";\n";

				output << "v_" << valueName << " = " << floatTypeStr << "(" << valueName << ");\n";
			}
		}
	}

	// Shader specialization.
	map<string, string> params;
	params.insert(pair<string, string>("DECLARATIONS", decl.str()));
	params.insert(pair<string, string>("SETUP", setup.str()));
	params.insert(pair<string, string>("OUTPUT", output.str()));
	params.insert(pair<string, string>("POSITION_FRAG_COLOR", "gl_Position"));

	StringTemplate tmpl(src);
	return tmpl.specialize(params);
}

// Specialize a shader for the fragment shader test case.
string ShaderCase::specializeFragmentShader(const char* src, const ValueBlock& valueBlock)
{
	ostringstream decl;
	ostringstream setup;
	ostringstream output;

	const bool  usesInout	  = usesShaderInoutQualifiers(m_targetVersion);
	const bool  customColorOut = usesInout;
	const char* fragIn		   = usesInout ? "in" : "varying";
	const char* fragColor	  = customColorOut ? "dEQP_FragColor" : "gl_FragColor";

	genCompareFunctions(decl, valueBlock, false);
	genCompareOp(output, fragColor, valueBlock, "", DE_NULL);

	if (customColorOut)
		decl << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";

	for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
	{
		const ShaderCase::Value& val		  = valueBlock.values[ndx];
		const char*				 valueName	= val.valueName.c_str();
		DataType				 floatType	= getDataTypeFloatScalars(val.dataType);
		const char*				 floatTypeStr = getDataTypeName(floatType);
		const char*				 refTypeStr   = getDataTypeName(val.dataType);

		if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
		{
			if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
				decl << fragIn << " " << floatTypeStr << " " << valueName << ";\n";
			else
			{
				decl << fragIn << " " << floatTypeStr << " v_" << valueName << ";\n";
				std::string offset =
					isDataTypeIntOrIVec(val.dataType) ?
						" * 1.0025" :
						""; // \todo [petri] bit of a hack to avoid errors in chop() due to varying interpolation
				setup << refTypeStr << " " << valueName << " = " << refTypeStr << "(v_" << valueName << offset
					  << ");\n";
			}
		}
		else if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
		{
			decl << "uniform " << refTypeStr << " ref_" << valueName << ";\n";
			decl << refTypeStr << " " << valueName << ";\n";
		}
	}

	/* \todo [2010-04-01 petri] Check all outputs. */

	// Shader specialization.
	map<string, string> params;
	params.insert(pair<string, string>("DECLARATIONS", decl.str()));
	params.insert(pair<string, string>("SETUP", setup.str()));
	params.insert(pair<string, string>("OUTPUT", output.str()));
	params.insert(pair<string, string>("POSITION_FRAG_COLOR", fragColor));

	StringTemplate tmpl(src);
	return tmpl.specialize(params);
}

void ShaderCase::specializeShaders(const char* vertexSource, const char* fragmentSource, string& outVertexSource,
								   string& outFragmentSource, const ValueBlock& valueBlock)
{
	const bool usesInout	  = usesShaderInoutQualifiers(m_targetVersion);
	const bool customColorOut = usesInout;

	// Vertex shader specialization.
	{
		ostringstream decl;
		ostringstream setup;
		const char*   vtxIn = usesInout ? "in" : "attribute";

		decl << vtxIn << " highp vec4 dEQP_Position;\n";

		for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
		{
			const ShaderCase::Value& val	 = valueBlock.values[ndx];
			const char*				 typeStr = getDataTypeName(val.dataType);

			if (val.storageType == ShaderCase::Value::STORAGE_INPUT)
			{
				if (getDataTypeScalarType(val.dataType) == TYPE_FLOAT)
				{
					decl << vtxIn << " " << typeStr << " " << val.valueName << ";\n";
				}
				else
				{
					DataType	floatType	= getDataTypeFloatScalars(val.dataType);
					const char* floatTypeStr = getDataTypeName(floatType);

					decl << vtxIn << " " << floatTypeStr << " a_" << val.valueName << ";\n";
					setup << typeStr << " " << val.valueName << " = " << typeStr << "(a_" << val.valueName << ");\n";
				}
			}
			else if (val.storageType == ShaderCase::Value::STORAGE_UNIFORM && val.valueName.find('.') == string::npos)
			{
				decl << "uniform " << typeStr << " " << val.valueName << ";\n";
			}
		}

		map<string, string> params;
		params.insert(pair<string, string>("VERTEX_DECLARATIONS", decl.str()));
		params.insert(pair<string, string>("VERTEX_SETUP", setup.str()));
		params.insert(pair<string, string>("VERTEX_OUTPUT", string("gl_Position = dEQP_Position;\n")));
		StringTemplate tmpl(vertexSource);
		outVertexSource = tmpl.specialize(params);
	}

	// Fragment shader specialization.
	{
		ostringstream decl;
		ostringstream output;
		const char*   fragColor = customColorOut ? "dEQP_FragColor" : "gl_FragColor";

		genCompareFunctions(decl, valueBlock, false);
		genCompareOp(output, fragColor, valueBlock, "", DE_NULL);

		if (customColorOut)
			decl << "layout(location = 0) out mediump vec4 dEQP_FragColor;\n";

		for (int ndx = 0; ndx < (int)valueBlock.values.size(); ndx++)
		{
			const ShaderCase::Value& val		= valueBlock.values[ndx];
			const char*				 valueName  = val.valueName.c_str();
			const char*				 refTypeStr = getDataTypeName(val.dataType);

			if (val.storageType == ShaderCase::Value::STORAGE_OUTPUT)
			{
				decl << "uniform " << refTypeStr << " ref_" << valueName << ";\n";
				decl << refTypeStr << " " << valueName << ";\n";
			}
			else if (val.storageType == ShaderCase::Value::STORAGE_UNIFORM && val.valueName.find('.') == string::npos)
			{
				decl << "uniform " << refTypeStr << " " << valueName << ";\n";
			}
		}

		map<string, string> params;
		params.insert(pair<string, string>("FRAGMENT_DECLARATIONS", decl.str()));
		params.insert(pair<string, string>("FRAGMENT_OUTPUT", output.str()));
		params.insert(pair<string, string>("FRAG_COLOR", fragColor));
		StringTemplate tmpl(fragmentSource);
		outFragmentSource = tmpl.specialize(params);
	}
}

void ShaderCase::dumpValues(const ValueBlock& valueBlock, int arrayNdx)
{
	vector<vector<float> > attribValues;

	int numValues = (int)valueBlock.values.size();
	for (int valNdx = 0; valNdx < numValues; valNdx++)
	{
		const ShaderCase::Value& val		= valueBlock.values[valNdx];
		const char*				 valueName  = val.valueName.c_str();
		DataType				 dataType   = val.dataType;
		int						 scalarSize = getDataTypeScalarSize(val.dataType);
		ostringstream			 result;

		result << "    ";
		if (val.storageType == Value::STORAGE_INPUT)
			result << "input ";
		else if (val.storageType == Value::STORAGE_UNIFORM)
			result << "uniform ";
		else if (val.storageType == Value::STORAGE_OUTPUT)
			result << "expected ";

		result << getDataTypeName(dataType) << " " << valueName << ":";

		if (isDataTypeScalar(dataType))
			result << " ";
		if (isDataTypeVector(dataType))
			result << " [ ";
		else if (isDataTypeMatrix(dataType))
			result << "\n";

		if (isDataTypeScalarOrVector(dataType))
		{
			for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++)
			{
				int					  elemNdx = (val.arrayLength == 1) ? 0 : arrayNdx;
				const Value::Element& e		  = val.elements[elemNdx * scalarSize + scalarNdx];
				result << ((scalarNdx != 0) ? ", " : "");

				if (isDataTypeFloatOrVec(dataType))
					result << e.float32;
				else if (isDataTypeIntOrIVec(dataType))
					result << e.int32;
				else if (isDataTypeBoolOrBVec(dataType))
					result << (e.bool32 ? "true" : "false");
			}
		}
		else if (isDataTypeMatrix(dataType))
		{
			int numRows = getDataTypeMatrixNumRows(dataType);
			int numCols = getDataTypeMatrixNumColumns(dataType);
			for (int rowNdx = 0; rowNdx < numRows; rowNdx++)
			{
				result << "       [ ";
				for (int colNdx = 0; colNdx < numCols; colNdx++)
				{
					int   elemNdx = (val.arrayLength == 1) ? 0 : arrayNdx;
					float v		  = val.elements[elemNdx * scalarSize + rowNdx * numCols + colNdx].float32;
					result << ((colNdx == 0) ? "" : ", ") << v;
				}
				result << " ]\n";
			}
		}

		if (isDataTypeScalar(dataType))
			result << "\n";
		else if (isDataTypeVector(dataType))
			result << " ]\n";

		m_testCtx.getLog() << TestLog::Message << result.str() << TestLog::EndMessage;
	}
}

} // sl
} // deqp