/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief FBO test utilities.
 *//*--------------------------------------------------------------------*/

#include "es3fFboTestUtil.hpp"
#include "sglrContextUtil.hpp"
#include "sglrGLContext.hpp"
#include "sglrReferenceContext.hpp"
#include "gluTextureUtil.hpp"
#include "tcuTextureUtil.hpp"
#include "deStringUtil.hpp"
#include "deMath.h"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

#include <limits>

namespace deqp
{
namespace gles3
{
namespace Functional
{
namespace FboTestUtil
{

using std::string;
using std::vector;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;

static rr::GenericVecType mapDataTypeToGenericVecType(glu::DataType type)
{
	switch (type)
	{
		case glu::TYPE_FLOAT_VEC4:	return rr::GENERICVECTYPE_FLOAT;
		case glu::TYPE_INT_VEC4:	return rr::GENERICVECTYPE_INT32;
		case glu::TYPE_UINT_VEC4:	return rr::GENERICVECTYPE_UINT32;
		default:
			DE_ASSERT(DE_FALSE);
			return rr::GENERICVECTYPE_LAST;
	}
}

template <typename T>
static tcu::Vector<T, 4> castVectorSaturate (const tcu::Vec4& in)
{
	return tcu::Vector<T, 4>((in.x() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.x() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.x()))),
	                         (in.y() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.y() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.y()))),
							 (in.z() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.z() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.z()))),
							 (in.w() + 0.5f >= std::numeric_limits<T>::max()) ? (std::numeric_limits<T>::max()) : ((in.w() - 0.5f <= std::numeric_limits<T>::min()) ? (std::numeric_limits<T>::min()) : (T(in.w()))));
}

FlatColorShader::FlatColorShader (glu::DataType outputType)
	: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
					<< sglr::pdec::Uniform("u_color", glu::TYPE_FLOAT_VEC4)
					<< sglr::pdec::VertexSource(
							"#version 300 es\n"
							"in highp vec4 a_position;\n"
							"void main (void)\n"
							"{\n"
							"	gl_Position = a_position;\n"
							"}\n")
					<< sglr::pdec::FragmentSource(
							string(
								"#version 300 es\n"
								"uniform highp vec4 u_color;\n"
								"layout(location = 0) out highp ") + glu::getDataTypeName(outputType) + " o_color;\n"
								"void main (void)\n"
								"{\n"
								"	o_color = " + glu::getDataTypeName(outputType) + "(u_color);\n"
								"}\n"))
	, m_outputType(outputType)
{
}

void FlatColorShader::setColor (sglr::Context& context, deUint32 program, const tcu::Vec4& color)
{
	deInt32 location = context.getUniformLocation(program, "u_color");

	context.useProgram(program);
	context.uniform4fv(location, 1, color.getPtr());
}

void FlatColorShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
{
	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		rr::VertexPacket& packet = *packets[packetNdx];

		packet.position = rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
	}
}

void FlatColorShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
{
	const tcu::Vec4		color	(m_uniforms[0].value.f4);
	const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
	const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);

	DE_UNREF(packets);

	if (m_outputType == glu::TYPE_FLOAT_VEC4)
	{
		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
	}
	else if (m_outputType == glu::TYPE_INT_VEC4)
	{
		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
	}
	else if (m_outputType == glu::TYPE_UINT_VEC4)
	{
		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
	}
	else
		DE_ASSERT(DE_FALSE);
}

GradientShader::GradientShader (glu::DataType outputType)
	: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
					<< sglr::pdec::Uniform("u_gradientMin", glu::TYPE_FLOAT_VEC4)
					<< sglr::pdec::Uniform("u_gradientMax", glu::TYPE_FLOAT_VEC4)
					<< sglr::pdec::VertexSource(
							"#version 300 es\n"
							"in highp vec4 a_position;\n"
							"in highp vec4 a_coord;\n"
							"out highp vec4 v_coord;\n"
							"void main (void)\n"
							"{\n"
							"	gl_Position = a_position;\n"
							"	v_coord = a_coord;\n"
							"}\n")
					<< sglr::pdec::FragmentSource(
							string(
								"#version 300 es\n"
								"in highp vec4 v_coord;\n"
								"uniform highp vec4 u_gradientMin;\n"
								"uniform highp vec4 u_gradientMax;\n"
								"layout(location = 0) out highp ") + glu::getDataTypeName(outputType) + " o_color;\n"
								"void main (void)\n"
								"{\n"
								"	highp float x = v_coord.x;\n"
								"	highp float y = v_coord.y;\n"
								"	highp float f0 = (x + y) * 0.5;\n"
								"	highp float f1 = 0.5 + (x - y) * 0.5;\n"
								"	highp vec4 fv = vec4(f0, f1, 1.0f-f0, 1.0f-f1);\n"
								"	o_color = " + glu::getDataTypeName(outputType) + "(u_gradientMin + (u_gradientMax-u_gradientMin)*fv);\n"
								"}\n"))
	, m_outputType(outputType)
{
}

void GradientShader::setGradient (sglr::Context& ctx, deUint32 program, const tcu::Vec4& gradientMin, const tcu::Vec4& gradientMax)
{
	ctx.useProgram(program);
	ctx.uniform4fv(ctx.getUniformLocation(program, "u_gradientMin"), 1, gradientMin.getPtr());
	ctx.uniform4fv(ctx.getUniformLocation(program, "u_gradientMax"), 1, gradientMax.getPtr());
}

void GradientShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
{
	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		rr::VertexPacket& packet = *packets[packetNdx];

		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
	}
}

void GradientShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
{
	const tcu::Vec4	gradientMin(m_uniforms[0].value.f4);
	const tcu::Vec4	gradientMax(m_uniforms[1].value.f4);

	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
	{
		const tcu::Vec4		coord	= rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
		const float			x		= coord.x();
		const float			y		= coord.y();
		const float			f0		= (x + y) * 0.5f;
		const float			f1		= 0.5f + (x - y) * 0.5f;
		const tcu::Vec4		fv		= Vec4(f0, f1, 1.0f-f0, 1.0f-f1);

		const tcu::Vec4		color	= gradientMin + (gradientMax-gradientMin) * fv;
		const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
		const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);

		if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
		else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
		else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
		else
			DE_ASSERT(DE_FALSE);
	}
}

static string genTexFragmentShader (const vector<glu::DataType>& samplerTypes, glu::DataType outputType)
{
	const char*			precision	= "highp";
	std::ostringstream	src;

	src << "#version 300 es\n"
		<< "layout(location = 0) out highp " << glu::getDataTypeName(outputType) << " o_color0;\n";

	src << "in highp vec2 v_coord;\n";

	for (int samplerNdx = 0; samplerNdx < (int)samplerTypes.size(); samplerNdx++)
	{
		src << "uniform " << precision << " " << glu::getDataTypeName(samplerTypes[samplerNdx]) << " u_sampler" << samplerNdx << ";\n";
		src << "uniform " << precision << " vec4 u_texScale" << samplerNdx << ";\n";
		src << "uniform " << precision << " vec4 u_texBias" << samplerNdx << ";\n";
	}

	// Output scale & bias
	src << "uniform " << precision << " vec4 u_outScale0;\n"
		<< "uniform " << precision << " vec4 u_outBias0;\n";

	src << "\n"
		<< "void main (void)\n"
		<< "{\n"
		<< "	" << precision << " vec4 out0 = vec4(0.0);\n";

	// Texture input fetch and combine.
	for (int inNdx = 0; inNdx < (int)samplerTypes.size(); inNdx++)
		src << "\tout0 += vec4("
			<< "texture(u_sampler" << inNdx << ", v_coord)) * u_texScale" << inNdx << " + u_texBias" << inNdx << ";\n";

	// Write output.
	src << "	o_color0 = " << glu::getDataTypeName(outputType) << "(out0 * u_outScale0 + u_outBias0);\n";

	src << "}\n";

	return src.str();
}

static sglr::pdec::ShaderProgramDeclaration genTexture2DShaderDecl (const DataTypes& samplerTypes, glu::DataType outputType)
{
	sglr::pdec::ShaderProgramDeclaration decl;

	decl << sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT);
	decl << sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT);
	decl << sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT);
	decl << sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType));

	decl << sglr::pdec::VertexSource(
		"#version 300 es\n"
		"in highp vec4 a_position;\n"
		"in highp vec2 a_coord;\n"
		"out highp vec2 v_coord;\n"
		"void main(void)\n"
		"{\n"
		"	gl_Position = a_position;\n"
		"	v_coord = a_coord;\n"
		"}\n");
	decl << sglr::pdec::FragmentSource(genTexFragmentShader(samplerTypes.vec, outputType));

	decl << sglr::pdec::Uniform("u_outScale0", glu::TYPE_FLOAT_VEC4);
	decl << sglr::pdec::Uniform("u_outBias0", glu::TYPE_FLOAT_VEC4);

	for (size_t ndx = 0; ndx < samplerTypes.vec.size(); ++ndx)
	{
		decl << sglr::pdec::Uniform(std::string("u_sampler")  + de::toString(ndx), samplerTypes.vec[ndx]);
		decl << sglr::pdec::Uniform(std::string("u_texScale") + de::toString(ndx), glu::TYPE_FLOAT_VEC4);
		decl << sglr::pdec::Uniform(std::string("u_texBias")  + de::toString(ndx), glu::TYPE_FLOAT_VEC4);
	}

	return decl;
}

Texture2DShader::Texture2DShader (const DataTypes& samplerTypes, glu::DataType outputType, const Vec4& outScale, const Vec4& outBias)
	: sglr::ShaderProgram	(genTexture2DShaderDecl(samplerTypes, outputType))
	, m_outScale			(outScale)
	, m_outBias				(outBias)
	, m_outputType			(outputType)
{
	m_inputs.resize(samplerTypes.vec.size());

	// Initialize units.
	for (int ndx = 0; ndx < (int)m_inputs.size(); ndx++)
	{
		m_inputs[ndx].unitNdx	= ndx;
		m_inputs[ndx].scale		= Vec4(1.0f);
		m_inputs[ndx].bias		= Vec4(0.0f);
	}
}

void Texture2DShader::setUnit (int inputNdx, int unitNdx)
{
	m_inputs[inputNdx].unitNdx = unitNdx;
}

void Texture2DShader::setTexScaleBias (int inputNdx, const Vec4& scale, const Vec4& bias)
{
	m_inputs[inputNdx].scale	= scale;
	m_inputs[inputNdx].bias		= bias;
}

void Texture2DShader::setOutScaleBias (const Vec4& scale, const Vec4& bias)
{
	m_outScale	= scale;
	m_outBias	= bias;
}

void Texture2DShader::setUniforms (sglr::Context& gl, deUint32 program) const
{
	gl.useProgram(program);

	for (int texNdx = 0; texNdx < (int)m_inputs.size(); texNdx++)
	{
		string	samplerName	= string("u_sampler") + de::toString(texNdx);
		string	scaleName	= string("u_texScale") + de::toString(texNdx);
		string	biasName	= string("u_texBias") + de::toString(texNdx);

		gl.uniform1i(gl.getUniformLocation(program, samplerName.c_str()), m_inputs[texNdx].unitNdx);
		gl.uniform4fv(gl.getUniformLocation(program, scaleName.c_str()), 1, m_inputs[texNdx].scale.getPtr());
		gl.uniform4fv(gl.getUniformLocation(program, biasName.c_str()), 1, m_inputs[texNdx].bias.getPtr());
	}

	gl.uniform4fv(gl.getUniformLocation(program, "u_outScale0"), 1, m_outScale.getPtr());
	gl.uniform4fv(gl.getUniformLocation(program, "u_outBias0"), 1, m_outBias.getPtr());
}

void Texture2DShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
{
	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		rr::VertexPacket& packet = *packets[packetNdx];

		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
	}
}

void Texture2DShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
{
	const tcu::Vec4 outScale (m_uniforms[0].value.f4);
	const tcu::Vec4 outBias	 (m_uniforms[1].value.f4);

	tcu::Vec2 texCoords[4];
	tcu::Vec4 colors[4];

	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		// setup tex coords
		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
		{
			const tcu::Vec4	coord = rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
			texCoords[fragNdx] = tcu::Vec2(coord.x(), coord.y());
		}

		// clear result
		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
			colors[fragNdx] = tcu::Vec4(0.0f);

		// sample each texture
		for (int ndx = 0; ndx < (int)m_inputs.size(); ndx++)
		{
			const sglr::rc::Texture2D*	tex		= m_uniforms[2 + ndx*3].sampler.tex2D;
			const tcu::Vec4				scale	(m_uniforms[2 + ndx*3 + 1].value.f4);
			const tcu::Vec4				bias	(m_uniforms[2 + ndx*3 + 2].value.f4);
			tcu::Vec4 tmpColors[4];

			tex->sample4(tmpColors, texCoords);

			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
				colors[fragNdx] += tmpColors[fragNdx] * scale + bias;
		}

		// write out
		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
		{
			const tcu::Vec4		color	= colors[fragNdx] * outScale + outBias;
			const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
			const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);

			if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
			else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
			else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
			else
				DE_ASSERT(DE_FALSE);
		}
	}
}

TextureCubeShader::TextureCubeShader (glu::DataType samplerType, glu::DataType outputType)
	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
							<< sglr::pdec::Uniform("u_coordMat", glu::TYPE_FLOAT_MAT3)
							<< sglr::pdec::Uniform("u_sampler0", samplerType)
							<< sglr::pdec::Uniform("u_scale", glu::TYPE_FLOAT_VEC4)
							<< sglr::pdec::Uniform("u_bias", glu::TYPE_FLOAT_VEC4)
							<< sglr::pdec::VertexSource(
									"#version 300 es\n"
									"in highp vec4 a_position;\n"
									"in mediump vec2 a_coord;\n"
									"uniform mat3 u_coordMat;\n"
									"out mediump vec3 v_coord;\n"
									"void main (void)\n"
									"{\n"
									"	gl_Position = a_position;\n"
									"	v_coord = u_coordMat * vec3(a_coord, 1.0);\n"
									"}\n")
							<< sglr::pdec::FragmentSource(
									string("") +
									"#version 300 es\n"
									"uniform highp " + glu::getDataTypeName(samplerType) + " u_sampler0;\n"
									"uniform highp vec4 u_scale;\n"
									"uniform highp vec4 u_bias;\n"
									"in mediump vec3 v_coord;\n"
									"layout(location = 0) out highp " + glu::getDataTypeName(outputType) + " o_color;\n"
									"void main (void)\n"
									"{\n"
									"	o_color = " + glu::getDataTypeName(outputType) + "(vec4(texture(u_sampler0, v_coord)) * u_scale + u_bias);\n"
									"}\n"))
	, m_texScale	(1.0f)
	, m_texBias		(0.0f)
	, m_outputType	(outputType)
{
}

void TextureCubeShader::setFace (tcu::CubeFace face)
{
	static const float s_cubeTransforms[][3*3] =
	{
		// Face -X: (x, y, 1) -> (-1, -(2*y-1), +(2*x-1))
		{  0.0f,  0.0f, -1.0f,
		   0.0f, -2.0f,  1.0f,
		   2.0f,  0.0f, -1.0f },
		// Face +X: (x, y, 1) -> (+1, -(2*y-1), -(2*x-1))
		{  0.0f,  0.0f,  1.0f,
		   0.0f, -2.0f,  1.0f,
		  -2.0f,  0.0f,  1.0f },
		// Face -Y: (x, y, 1) -> (+(2*x-1), -1, -(2*y-1))
		{  2.0f,  0.0f, -1.0f,
		   0.0f,  0.0f, -1.0f,
		   0.0f, -2.0f,  1.0f },
		// Face +Y: (x, y, 1) -> (+(2*x-1), +1, +(2*y-1))
		{  2.0f,  0.0f, -1.0f,
		   0.0f,  0.0f,  1.0f,
		   0.0f,  2.0f, -1.0f },
		// Face -Z: (x, y, 1) -> (-(2*x-1), -(2*y-1), -1)
		{ -2.0f,  0.0f,  1.0f,
		   0.0f, -2.0f,  1.0f,
		   0.0f,  0.0f, -1.0f },
		// Face +Z: (x, y, 1) -> (+(2*x-1), -(2*y-1), +1)
		{  2.0f,  0.0f, -1.0f,
		   0.0f, -2.0f,  1.0f,
		   0.0f,  0.0f,  1.0f }
	};
	DE_ASSERT(de::inBounds<int>(face, 0, tcu::CUBEFACE_LAST));
	m_coordMat = tcu::Mat3(s_cubeTransforms[face]);
}

void TextureCubeShader::setTexScaleBias (const Vec4& scale, const Vec4& bias)
{
	m_texScale	= scale;
	m_texBias	= bias;
}

void TextureCubeShader::setUniforms (sglr::Context& gl, deUint32 program) const
{
	gl.useProgram(program);

	gl.uniform1i(gl.getUniformLocation(program, "u_sampler0"), 0);
	gl.uniformMatrix3fv(gl.getUniformLocation(program, "u_coordMat"), 1, GL_FALSE, m_coordMat.getColumnMajorData().getPtr());
	gl.uniform4fv(gl.getUniformLocation(program, "u_scale"), 1, m_texScale.getPtr());
	gl.uniform4fv(gl.getUniformLocation(program, "u_bias"), 1, m_texBias.getPtr());
}

void TextureCubeShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
{
	tcu::Mat3 texCoordMat = tcu::Mat3(m_uniforms[0].value.m3);

	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		rr::VertexPacket&	packet	= *packets[packetNdx];
		tcu::Vec2			a_coord = rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx).xy();
		tcu::Vec3			v_coord = texCoordMat * tcu::Vec3(a_coord.x(), a_coord.y(), 1.0f);

		packet.position				= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
		packet.outputs[0]			= tcu::Vec4(v_coord.x(), v_coord.y(), v_coord.z(), 0.0f);
	}
}

void TextureCubeShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
{
	const tcu::Vec4 texScale (m_uniforms[2].value.f4);
	const tcu::Vec4 texBias	 (m_uniforms[3].value.f4);

	tcu::Vec3 texCoords[4];
	tcu::Vec4 colors[4];

	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		const sglr::rc::TextureCube* tex = m_uniforms[1].sampler.texCube;

		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
		{
			const tcu::Vec4	coord = rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
			texCoords[fragNdx] = tcu::Vec3(coord.x(), coord.y(), coord.z());
		}

		tex->sample4(colors, texCoords);

		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
		{
			const tcu::Vec4		color	= colors[fragNdx] * texScale + texBias;
			const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
			const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);

			if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
			else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
			else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
			else
				DE_ASSERT(DE_FALSE);
		}
	}
}

Texture2DArrayShader::Texture2DArrayShader (glu::DataType samplerType, glu::DataType outputType)
	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
							<< sglr::pdec::Uniform("u_sampler0", samplerType)
							<< sglr::pdec::Uniform("u_scale", glu::TYPE_FLOAT_VEC4)
							<< sglr::pdec::Uniform("u_bias", glu::TYPE_FLOAT_VEC4)
							<< sglr::pdec::Uniform("u_layer", glu::TYPE_INT)
							<< sglr::pdec::VertexSource(
									"#version 300 es\n"
									"in highp vec4 a_position;\n"
									"in highp vec2 a_coord;\n"
									"out highp vec2 v_coord;\n"
									"void main (void)\n"
									"{\n"
									"	gl_Position = a_position;\n"
									"	v_coord = a_coord;\n"
									"}\n")
							<< sglr::pdec::FragmentSource(
									string("") +
									"#version 300 es\n"
									"uniform highp " + glu::getDataTypeName(samplerType) + " u_sampler0;\n"
									"uniform highp vec4 u_scale;\n"
									"uniform highp vec4 u_bias;\n"
									"uniform highp int u_layer;\n"
									"in highp vec2 v_coord;\n"
									"layout(location = 0) out highp " + glu::getDataTypeName(outputType) + " o_color;\n"
									"void main (void)\n"
									"{\n"
									"	o_color = " + glu::getDataTypeName(outputType) + "(vec4(texture(u_sampler0, vec3(v_coord, u_layer))) * u_scale + u_bias);\n"
									"}\n"))
	, m_texScale	(1.0f)
	, m_texBias		(0.0f)
	, m_layer		(0)
	, m_outputType	(outputType)
{
}

void Texture2DArrayShader::setLayer (int layer)
{
	m_layer = layer;
}

void Texture2DArrayShader::setTexScaleBias (const Vec4& scale, const Vec4& bias)
{
	m_texScale	= scale;
	m_texBias	= bias;
}

void Texture2DArrayShader::setUniforms (sglr::Context& gl, deUint32 program) const
{
	gl.useProgram(program);

	gl.uniform1i	(gl.getUniformLocation(program, "u_sampler0"),	0);
	gl.uniform1i	(gl.getUniformLocation(program, "u_layer"),		m_layer);
	gl.uniform4fv	(gl.getUniformLocation(program, "u_scale"),		1, m_texScale.getPtr());
	gl.uniform4fv	(gl.getUniformLocation(program, "u_bias"),		1, m_texBias.getPtr());
}

void Texture2DArrayShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
{
	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		rr::VertexPacket& packet = *packets[packetNdx];

		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
	}
}

void Texture2DArrayShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
{
	const tcu::Vec4 texScale	(m_uniforms[1].value.f4);
	const tcu::Vec4 texBias		(m_uniforms[2].value.f4);
	const int		layer		= m_uniforms[3].value.i;

	tcu::Vec3 texCoords[4];
	tcu::Vec4 colors[4];

	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		const sglr::rc::Texture2DArray* tex = m_uniforms[0].sampler.tex2DArray;

		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
		{
			const tcu::Vec4	coord = rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
			texCoords[fragNdx] = tcu::Vec3(coord.x(), coord.y(), float(layer));
		}

		tex->sample4(colors, texCoords);

		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
		{
			const tcu::Vec4		color	= colors[fragNdx] * texScale + texBias;
			const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
			const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);

			if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
			else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
			else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
			else
				DE_ASSERT(DE_FALSE);
		}
	}
}

Texture3DShader::Texture3DShader (glu::DataType samplerType, glu::DataType outputType)
	: sglr::ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
							<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
							<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
							<< sglr::pdec::Uniform("u_sampler0", samplerType)
							<< sglr::pdec::Uniform("u_scale", glu::TYPE_FLOAT_VEC4)
							<< sglr::pdec::Uniform("u_bias", glu::TYPE_FLOAT_VEC4)
							<< sglr::pdec::Uniform("u_depth", glu::TYPE_FLOAT)
							<< sglr::pdec::VertexSource(
									"#version 300 es\n"
									"in highp vec4 a_position;\n"
									"in highp vec2 a_coord;\n"
									"out highp vec2 v_coord;\n"
									"void main (void)\n"
									"{\n"
									"	gl_Position = a_position;\n"
									"	v_coord = a_coord;\n"
									"}\n")
							<< sglr::pdec::FragmentSource(
									string("") +
									"#version 300 es\n"
									"uniform highp " + glu::getDataTypeName(samplerType) + " u_sampler0;\n"
									"uniform highp vec4 u_scale;\n"
									"uniform highp vec4 u_bias;\n"
									"uniform highp float u_depth;\n"
									"in highp vec2 v_coord;\n"
									"layout(location = 0) out highp " + glu::getDataTypeName(outputType) + " o_color;\n"
									"void main (void)\n"
									"{\n"
									"	o_color = " + glu::getDataTypeName(outputType) + "(vec4(texture(u_sampler0, vec3(v_coord, u_depth))) * u_scale + u_bias);\n"
									"}\n"))
	, m_texScale	(1.0f)
	, m_texBias		(0.0f)
	, m_depth		(0.0f)
	, m_outputType	(outputType)
{
}

void Texture3DShader::setDepth (float depth)
{
	m_depth = depth;
}

void Texture3DShader::setTexScaleBias (const Vec4& scale, const Vec4& bias)
{
	m_texScale	= scale;
	m_texBias	= bias;
}

void Texture3DShader::setUniforms (sglr::Context& gl, deUint32 program) const
{
	gl.useProgram(program);

	gl.uniform1i	(gl.getUniformLocation(program, "u_sampler0"),	0);
	gl.uniform1f	(gl.getUniformLocation(program, "u_depth"),		m_depth);
	gl.uniform4fv	(gl.getUniformLocation(program, "u_scale"),		1, m_texScale.getPtr());
	gl.uniform4fv	(gl.getUniformLocation(program, "u_bias"),		1, m_texBias.getPtr());
}

void Texture3DShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
{
	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		rr::VertexPacket& packet = *packets[packetNdx];

		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
	}
}

void Texture3DShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
{
	const tcu::Vec4 texScale	(m_uniforms[1].value.f4);
	const tcu::Vec4 texBias		(m_uniforms[2].value.f4);
	const float		depth		= m_uniforms[3].value.f;

	tcu::Vec3 texCoords[4];
	tcu::Vec4 colors[4];

	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		const sglr::rc::Texture3D* tex = m_uniforms[0].sampler.tex3D;

		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
		{
			const tcu::Vec4	coord = rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
			texCoords[fragNdx] = tcu::Vec3(coord.x(), coord.y(), depth);
		}

		tex->sample4(colors, texCoords);

		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
		{
			const tcu::Vec4		color	= colors[fragNdx] * texScale + texBias;
			const tcu::IVec4	icolor	= castVectorSaturate<deInt32>(color);
			const tcu::UVec4	uicolor	= castVectorSaturate<deUint32>(color);

			if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
			else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
			else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
			else
				DE_ASSERT(DE_FALSE);
		}
	}
}

DepthGradientShader::DepthGradientShader (glu::DataType outputType)
	: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::FragmentOutput(mapDataTypeToGenericVecType(outputType))
					<< sglr::pdec::Uniform("u_maxGradient", glu::TYPE_FLOAT)
					<< sglr::pdec::Uniform("u_minGradient", glu::TYPE_FLOAT)
					<< sglr::pdec::Uniform("u_color", glu::TYPE_FLOAT_VEC4)
					<< sglr::pdec::VertexSource(
							"#version 300 es\n"
							"in highp vec4 a_position;\n"
							"in highp vec4 a_coord;\n"
							"out highp vec4 v_coord;\n"
							"void main (void)\n"
							"{\n"
							"	gl_Position = a_position;\n"
							"	v_coord = a_coord;\n"
							"}\n")
					<< sglr::pdec::FragmentSource(
							string(
								"#version 300 es\n"
								"in highp vec4 v_coord;\n"
								"uniform highp float u_minGradient;\n"
								"uniform highp float u_maxGradient;\n"
								"uniform highp vec4 u_color;\n"
								"layout(location = 0) out highp ") + glu::getDataTypeName(outputType) + " o_color;\n"
								"void main (void)\n"
								"{\n"
								"	highp float x = v_coord.x;\n"
								"	highp float y = v_coord.y;\n"
								"	highp float f0 = (x + y) * 0.5;\n"
								"	gl_FragDepth = u_minGradient + (u_maxGradient-u_minGradient)*f0;\n"
								"	o_color = " + glu::getDataTypeName(outputType) + "(u_color);\n"
								"}\n"))
	, m_outputType	(outputType)
	, u_minGradient	(getUniformByName("u_minGradient"))
	, u_maxGradient	(getUniformByName("u_maxGradient"))
	, u_color		(getUniformByName("u_color"))
{
}

void DepthGradientShader::setUniforms (sglr::Context& ctx, deUint32 program, const float gradientMin, const float gradientMax, const tcu::Vec4& color)
{
	ctx.useProgram(program);
	ctx.uniform1fv(ctx.getUniformLocation(program, "u_minGradient"), 1, &gradientMin);
	ctx.uniform1fv(ctx.getUniformLocation(program, "u_maxGradient"), 1, &gradientMax);
	ctx.uniform4fv(ctx.getUniformLocation(program, "u_color"), 1, color.getPtr());
}

void DepthGradientShader::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
{
	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		rr::VertexPacket& packet = *packets[packetNdx];

		packet.position		= rr::readVertexAttribFloat(inputs[0], packet.instanceNdx, packet.vertexNdx);
		packet.outputs[0]	= rr::readVertexAttribFloat(inputs[1], packet.instanceNdx, packet.vertexNdx);
	}
}

void DepthGradientShader::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
{
	const float			gradientMin	(u_minGradient.value.f);
	const float			gradientMax	(u_maxGradient.value.f);
	const tcu::Vec4		color		(u_color.value.f4);
	const tcu::IVec4	icolor		(castVectorSaturate<deInt32>(color));
	const tcu::UVec4	uicolor		(castVectorSaturate<deUint32>(color));

	// running this shader without a depth buffer does not make any sense
	DE_ASSERT(context.fragmentDepths);

	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
	{
		const tcu::Vec4		coord	= rr::readTriangleVarying<float>(packets[packetNdx], context, 0, fragNdx);
		const float			x		= coord.x();
		const float			y		= coord.y();
		const float			f0		= (x + y) * 0.5f;

		rr::writeFragmentDepth(context, packetNdx, fragNdx, 0, gradientMin + (gradientMax-gradientMin) * f0);

		if (m_outputType == glu::TYPE_FLOAT_VEC4)			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
		else if (m_outputType == glu::TYPE_INT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, icolor);
		else if (m_outputType == glu::TYPE_UINT_VEC4)		rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, uicolor);
		else
			DE_ASSERT(DE_FALSE);
	}
}

void clearColorBuffer (sglr::Context& ctx, const tcu::TextureFormat& format, const tcu::Vec4& value)
{
	const tcu::TextureChannelClass fmtClass = tcu::getTextureChannelClass(format.type);

	switch (fmtClass)
	{
		case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
		case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
		case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
			ctx.clearBufferfv(GL_COLOR, 0, value.getPtr());
			break;

		case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
			ctx.clearBufferuiv(GL_COLOR, 0, value.asUint().getPtr());
			break;

		case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
			ctx.clearBufferiv(GL_COLOR, 0, value.asInt().getPtr());
			break;

		default:
			DE_ASSERT(DE_FALSE);
	}
}

void readPixels (sglr::Context& ctx, tcu::Surface& dst, int x, int y, int width, int height, const tcu::TextureFormat& format, const tcu::Vec4& scale, const tcu::Vec4& bias)
{
	tcu::TextureFormat		readFormat		= getFramebufferReadFormat(format);
	glu::TransferFormat		transferFmt		= glu::getTransferFormat(readFormat);
	int						alignment		= 4; // \note GL_PACK_ALIGNMENT = 4 is assumed.
	int						rowSize			= deAlign32(readFormat.getPixelSize()*width, alignment);
	vector<deUint8>			data			(rowSize*height);

	ctx.readPixels(x, y, width, height, transferFmt.format, transferFmt.dataType, &data[0]);

	// Convert to surface.
	tcu::ConstPixelBufferAccess src(readFormat, width, height, 1, rowSize, 0, &data[0]);

	dst.setSize(width, height);
	tcu::PixelBufferAccess dstAccess = dst.getAccess();

	for (int yo = 0; yo < height; yo++)
	for (int xo = 0; xo < width; xo++)
		dstAccess.setPixel(src.getPixel(xo, yo) * scale + bias, xo, yo);
}

static const char* getFboIncompleteReasonName (deUint32 reason)
{
	switch (reason)
	{
		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:			return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:	return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:			return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
		case GL_FRAMEBUFFER_UNSUPPORTED:					return "GL_FRAMEBUFFER_UNSUPPORTED";
		case GL_FRAMEBUFFER_COMPLETE:						return "GL_FRAMEBUFFER_COMPLETE";
		default:											return "UNKNOWN";
	}
}

FboIncompleteException::FboIncompleteException (deUint32 reason, const char* file, int line)
	: TestError		("Framebuffer is not complete", getFboIncompleteReasonName(reason), file, line)
	, m_reason		(reason)
{
}

const char* getFormatName (deUint32 format)
{
	switch (format)
	{
		case GL_RGB565:				return "rgb565";
		case GL_RGB5_A1:			return "rgb5_a1";
		case GL_RGBA4:				return "rgba4";
		case GL_DEPTH_COMPONENT16:	return "depth_component16";
		case GL_STENCIL_INDEX8:		return "stencil_index8";
		case GL_RGBA32F:			return "rgba32f";
		case GL_RGBA32I:			return "rgba32i";
		case GL_RGBA32UI:			return "rgba32ui";
		case GL_RGBA16F:			return "rgba16f";
		case GL_RGBA16I:			return "rgba16i";
		case GL_RGBA16UI:			return "rgba16ui";
		case GL_RGBA8:				return "rgba8";
		case GL_RGBA8I:				return "rgba8i";
		case GL_RGBA8UI:			return "rgba8ui";
		case GL_SRGB8_ALPHA8:		return "srgb8_alpha8";
		case GL_RGB10_A2:			return "rgb10_a2";
		case GL_RGB10_A2UI:			return "rgb10_a2ui";
		case GL_RGBA8_SNORM:		return "rgba8_snorm";
		case GL_RGB8:				return "rgb8";
		case GL_R11F_G11F_B10F:		return "r11f_g11f_b10f";
		case GL_RGB32F:				return "rgb32f";
		case GL_RGB32I:				return "rgb32i";
		case GL_RGB32UI:			return "rgb32ui";
		case GL_RGB16F:				return "rgb16f";
		case GL_RGB16I:				return "rgb16i";
		case GL_RGB16UI:			return "rgb16ui";
		case GL_RGB8_SNORM:			return "rgb8_snorm";
		case GL_RGB8I:				return "rgb8i";
		case GL_RGB8UI:				return "rgb8ui";
		case GL_SRGB8:				return "srgb8";
		case GL_RGB9_E5:			return "rgb9_e5";
		case GL_RG32F:				return "rg32f";
		case GL_RG32I:				return "rg32i";
		case GL_RG32UI:				return "rg32ui";
		case GL_RG16F:				return "rg16f";
		case GL_RG16I:				return "rg16i";
		case GL_RG16UI:				return "rg16ui";
		case GL_RG8:				return "rg8";
		case GL_RG8I:				return "rg8i";
		case GL_RG8UI:				return "rg8ui";
		case GL_RG8_SNORM:			return "rg8_snorm";
		case GL_R32F:				return "r32f";
		case GL_R32I:				return "r32i";
		case GL_R32UI:				return "r32ui";
		case GL_R16F:				return "r16f";
		case GL_R16I:				return "r16i";
		case GL_R16UI:				return "r16ui";
		case GL_R8:					return "r8";
		case GL_R8I:				return "r8i";
		case GL_R8UI:				return "r8ui";
		case GL_R8_SNORM:			return "r8_snorm";
		case GL_DEPTH_COMPONENT32F:	return "depth_component32f";
		case GL_DEPTH_COMPONENT24:	return "depth_component24";
		case GL_DEPTH32F_STENCIL8:	return "depth32f_stencil8";
		case GL_DEPTH24_STENCIL8:	return "depth24_stencil8";

		default:
			TCU_FAIL("Unknown format");
	}
}

glu::DataType getFragmentOutputType (const tcu::TextureFormat& format)
{
	switch (tcu::getTextureChannelClass(format.type))
	{
		case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
		case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
		case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
			return glu::TYPE_FLOAT_VEC4;

		case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
			return glu::TYPE_UINT_VEC4;

		case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
			return glu::TYPE_INT_VEC4;

		default:
			DE_ASSERT(!"Unknown format");
			return glu::TYPE_LAST;
	}
}

tcu::TextureFormat getFramebufferReadFormat (const tcu::TextureFormat& format)
{
	switch (tcu::getTextureChannelClass(format.type))
	{
		case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
			return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::FLOAT);

		case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
		case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
			return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);

		case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
			return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNSIGNED_INT32);

		case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
			return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::SIGNED_INT32);

		default:
			DE_ASSERT(!"Unknown format");
			return tcu::TextureFormat();
	}
}

static int calculateU8ConversionError (int srcBits)
{
	if (srcBits > 0)
	{
		const int clampedBits	= de::clamp<int>(srcBits, 0, 8);
		const int srcMaxValue	= de::max((1<<clampedBits) - 1, 1);
		const int error			= int(deFloatCeil(255.0f * 2.0f / float(srcMaxValue)));

		return de::clamp<int>(error, 0, 255);
	}
	else
		return 1;
}

tcu::RGBA getFormatThreshold (const tcu::TextureFormat& format)
{
	const tcu::IVec4 bits = tcu::getTextureFormatMantissaBitDepth(format);

	return tcu::RGBA(calculateU8ConversionError(bits.x()),
					 calculateU8ConversionError(bits.y()),
					 calculateU8ConversionError(bits.z()),
					 calculateU8ConversionError(bits.w()));
}

tcu::RGBA getFormatThreshold (deUint32 glFormat)
{
	const tcu::TextureFormat format = glu::mapGLInternalFormat(glFormat);

	return getFormatThreshold(format);
}

static int getToSRGB8ConversionError (int srcBits)
{
	// \note These are pre-computed based on simulation results.
	static const int errors[] =
	{
		1,		// 0 bits - rounding
		255,	// 1 bits
		157,	// 2 bits
		106,	// 3 bits
		74,		// 4 bits
		51,		// 5 bits
		34,		// 6 bits
		22,		// 7 bits
		13,		// 8 bits
		7,		// 9 bits
		4,		// 10 bits
		3,		// 11 bits
		2,		// 12 bits
		// 1 from this on
	};

	DE_ASSERT(srcBits >= 0);
	if (srcBits < DE_LENGTH_OF_ARRAY(errors))
		return errors[srcBits];
	else
		return 1;
}

tcu::RGBA getToSRGBConversionThreshold (const tcu::TextureFormat& src, const tcu::TextureFormat& dst)
{
	// Only SRGB8 and SRGB8_ALPHA8 formats are supported.
	DE_ASSERT(dst.type == tcu::TextureFormat::UNORM_INT8 && (dst.order == tcu::TextureFormat::sRGB || dst.order == tcu::TextureFormat::sRGBA));

	const tcu::IVec4	bits		= tcu::getTextureFormatMantissaBitDepth(src);
	const bool			dstHasAlpha	= dst.order == tcu::TextureFormat::sRGBA;

	return tcu::RGBA(getToSRGB8ConversionError(bits.x()),
					 getToSRGB8ConversionError(bits.y()),
					 getToSRGB8ConversionError(bits.z()),
					 dstHasAlpha ? calculateU8ConversionError(bits.w()) : 0);
}

} // FboTestUtil
} // Functional
} // gles3
} // deqp