/*-------------------------------------------------------------------------
 * 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 Fragment shader output tests.
 *
 * \todo [2012-04-10 pyry] Missing:
 *  + non-contiguous attachments in framebuffer
 *//*--------------------------------------------------------------------*/

#include "es3fFragmentOutputTests.hpp"
#include "gluShaderUtil.hpp"
#include "gluShaderProgram.hpp"
#include "gluTextureUtil.hpp"
#include "gluStrUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuTexture.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVector.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuImageCompare.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deMath.h"

// For getFormatName() \todo [pyry] Move to glu?
#include "es3fFboTestUtil.hpp"

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

namespace deqp
{
namespace gles3
{
namespace Functional
{

using std::vector;
using std::string;
using tcu::IVec2;
using tcu::IVec4;
using tcu::UVec2;
using tcu::UVec4;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::BVec4;
using tcu::TestLog;
using FboTestUtil::getFormatName;
using FboTestUtil::getFramebufferReadFormat;

struct BufferSpec
{
	BufferSpec (void)
		: format	(GL_NONE)
		, width		(0)
		, height	(0)
		, samples	(0)
	{
	}

	BufferSpec (deUint32 format_, int width_, int height_, int samples_)
		: format	(format_)
		, width		(width_)
		, height	(height_)
		, samples	(samples_)
	{
	}

	deUint32	format;
	int			width;
	int			height;
	int			samples;
};

struct FragmentOutput
{
	FragmentOutput (void)
		: type			(glu::TYPE_LAST)
		, precision		(glu::PRECISION_LAST)
		, location		(0)
		, arrayLength	(0)
	{
	}

	FragmentOutput (glu::DataType type_, glu::Precision precision_, int location_, int arrayLength_ = 0)
		: type			(type_)
		, precision		(precision_)
		, location		(location_)
		, arrayLength	(arrayLength_)
	{
	}

	glu::DataType	type;
	glu::Precision	precision;
	int				location;
	int				arrayLength;	//!< 0 if not an array.
};

struct OutputVec
{
	vector<FragmentOutput> outputs;

	OutputVec& operator<< (const FragmentOutput& output)
	{
		outputs.push_back(output);
		return *this;
	}

	vector<FragmentOutput> toVec (void) const
	{
		return outputs;
	}
};

class FragmentOutputCase : public TestCase
{
public:
								FragmentOutputCase			(Context& context, const char* name, const char* desc, const vector<BufferSpec>& fboSpec, const vector<FragmentOutput>& outputs);
								~FragmentOutputCase			(void);

	void						init						(void);
	void						deinit						(void);
	IterateResult				iterate						(void);

private:
								FragmentOutputCase			(const FragmentOutputCase& other);
	FragmentOutputCase&			operator=					(const FragmentOutputCase& other);

	vector<BufferSpec>			m_fboSpec;
	vector<FragmentOutput>		m_outputs;

	glu::ShaderProgram*			m_program;
	deUint32					m_framebuffer;
	vector<deUint32>			m_renderbuffers;
};

FragmentOutputCase::FragmentOutputCase (Context& context, const char* name, const char* desc, const vector<BufferSpec>& fboSpec, const vector<FragmentOutput>& outputs)
	: TestCase		(context, name, desc)
	, m_fboSpec		(fboSpec)
	, m_outputs		(outputs)
	, m_program		(DE_NULL)
	, m_framebuffer	(0)
{
}

FragmentOutputCase::~FragmentOutputCase (void)
{
	deinit();
}

static glu::ShaderProgram* createProgram (const glu::RenderContext& context, const vector<FragmentOutput>& outputs)
{
	std::ostringstream	vtx;
	std::ostringstream	frag;

	vtx << "#version 300 es\n"
		<< "in highp vec4 a_position;\n";
	frag << "#version 300 es\n";

	// Input-output declarations.
	for (int outNdx = 0; outNdx < (int)outputs.size(); outNdx++)
	{
		const FragmentOutput&	output		= outputs[outNdx];
		bool					isArray		= output.arrayLength > 0;
		const char*				typeName	= glu::getDataTypeName(output.type);
		const char*				outputPrec	= glu::getPrecisionName(output.precision);
		bool					isFloat		= glu::isDataTypeFloatOrVec(output.type);
		const char*				interp		= isFloat ? "smooth" : "flat";
		const char*				interpPrec	= isFloat ? "highp" : outputPrec;

		if (isArray)
		{
			for (int elemNdx = 0; elemNdx < output.arrayLength; elemNdx++)
			{
				vtx << "in " << interpPrec << " " << typeName << " in" << outNdx << "_" << elemNdx << ";\n"
					<< interp << " out " << interpPrec << " " << typeName << " var" << outNdx << "_" << elemNdx << ";\n";
				frag << interp << " in " << interpPrec << " " << typeName << " var" << outNdx << "_" << elemNdx << ";\n";
			}
			frag << "layout(location = " << output.location << ") out " << outputPrec << " " << typeName << " out" << outNdx << "[" << output.arrayLength << "];\n";
		}
		else
		{
			vtx << "in " << interpPrec << " " << typeName << " in" << outNdx << ";\n"
				<< interp << " out " << interpPrec << " " << typeName << " var" << outNdx << ";\n";
			frag << interp << " in " << interpPrec << " " << typeName << " var" << outNdx << ";\n"
				 << "layout(location = " << output.location << ") out " << outputPrec << " " << typeName << " out" << outNdx << ";\n";
		}
	}

	vtx << "\nvoid main()\n{\n";
	frag << "\nvoid main()\n{\n";

	vtx << "	gl_Position = a_position;\n";

	// Copy body
	for (int outNdx = 0; outNdx < (int)outputs.size(); outNdx++)
	{
		const FragmentOutput&	output		= outputs[outNdx];
		bool					isArray		= output.arrayLength > 0;

		if (isArray)
		{
			for (int elemNdx = 0; elemNdx < output.arrayLength; elemNdx++)
			{
				vtx << "\tvar" << outNdx << "_" << elemNdx << " = in" << outNdx << "_" << elemNdx << ";\n";
				frag << "\tout" << outNdx << "[" << elemNdx << "] = var" << outNdx << "_" << elemNdx << ";\n";
			}
		}
		else
		{
			vtx << "\tvar" << outNdx << " = in" << outNdx << ";\n";
			frag << "\tout" << outNdx << " = var" << outNdx << ";\n";
		}
	}

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

	return new glu::ShaderProgram(context, glu::makeVtxFragSources(vtx.str(), frag.str()));
}

void FragmentOutputCase::init (void)
{
	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
	TestLog&				log		= m_testCtx.getLog();

	// Check that all attachments are supported
	for (std::vector<BufferSpec>::const_iterator bufIter = m_fboSpec.begin(); bufIter != m_fboSpec.end(); ++bufIter)
	{
		if (!glu::isSizedFormatColorRenderable(m_context.getRenderContext(), m_context.getContextInfo(), bufIter->format))
			throw tcu::NotSupportedError("Unsupported attachment format");
	}

	DE_ASSERT(!m_program);
	m_program = createProgram(m_context.getRenderContext(), m_outputs);

	log << *m_program;
	if (!m_program->isOk())
		TCU_FAIL("Compile failed");

	// Print render target info to log.
	log << TestLog::Section("Framebuffer", "Framebuffer configuration");

	for (int ndx = 0; ndx < (int)m_fboSpec.size(); ndx++)
		log << TestLog::Message << "COLOR_ATTACHMENT" << ndx << ": "
								<< glu::getPixelFormatStr(m_fboSpec[ndx].format) << ", "
								<< m_fboSpec[ndx].width << "x" << m_fboSpec[ndx].height << ", "
								<< m_fboSpec[ndx].samples << " samples"
			<< TestLog::EndMessage;

	log << TestLog::EndSection;

	// Create framebuffer.
	m_renderbuffers.resize(m_fboSpec.size(), 0);
	gl.genFramebuffers(1, &m_framebuffer);
	gl.genRenderbuffers((int)m_renderbuffers.size(), &m_renderbuffers[0]);

	gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);

	for (int bufNdx = 0; bufNdx < (int)m_renderbuffers.size(); bufNdx++)
	{
		deUint32			rbo			= m_renderbuffers[bufNdx];
		const BufferSpec&	bufSpec		= m_fboSpec[bufNdx];
		deUint32			attachment	= GL_COLOR_ATTACHMENT0+bufNdx;

		gl.bindRenderbuffer(GL_RENDERBUFFER, rbo);
		gl.renderbufferStorageMultisample(GL_RENDERBUFFER, bufSpec.samples, bufSpec.format, bufSpec.width, bufSpec.height);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rbo);
	}
	GLU_EXPECT_NO_ERROR(gl.getError(), "After framebuffer setup");

	deUint32 fboStatus = gl.checkFramebufferStatus(GL_FRAMEBUFFER);
	if (fboStatus == GL_FRAMEBUFFER_UNSUPPORTED)
		throw tcu::NotSupportedError("Framebuffer not supported", "", __FILE__, __LINE__);
	else if (fboStatus != GL_FRAMEBUFFER_COMPLETE)
		throw tcu::TestError((string("Incomplete framebuffer: ") + glu::getFramebufferStatusStr(fboStatus).toString()).c_str(), "", __FILE__, __LINE__);

	gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
	GLU_EXPECT_NO_ERROR(gl.getError(), "After init");
}

void FragmentOutputCase::deinit (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	if (m_framebuffer)
	{
		gl.deleteFramebuffers(1, &m_framebuffer);
		m_framebuffer = 0;
	}

	if (!m_renderbuffers.empty())
	{
		gl.deleteRenderbuffers((int)m_renderbuffers.size(), &m_renderbuffers[0]);
		m_renderbuffers.clear();
	}

	delete m_program;
	m_program = DE_NULL;
}

static IVec2 getMinSize (const vector<BufferSpec>& fboSpec)
{
	IVec2 minSize(0x7fffffff, 0x7fffffff);
	for (vector<BufferSpec>::const_iterator i = fboSpec.begin(); i != fboSpec.end(); i++)
	{
		minSize.x() = de::min(minSize.x(), i->width);
		minSize.y() = de::min(minSize.y(), i->height);
	}
	return minSize;
}

static int getNumInputVectors (const vector<FragmentOutput>& outputs)
{
	int numVecs = 0;
	for (vector<FragmentOutput>::const_iterator i = outputs.begin(); i != outputs.end(); i++)
		numVecs += (i->arrayLength > 0 ? i->arrayLength : 1);
	return numVecs;
}

static Vec2 getFloatRange (glu::Precision precision)
{
	// \todo [2012-04-09 pyry] Not quite the full ranges.
	static const Vec2 ranges[] =
	{
		Vec2(-2.0f, 2.0f),
		Vec2(-16000.0f, 16000.0f),
		Vec2(-1e35f, 1e35f)
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(ranges) == glu::PRECISION_LAST);
	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(ranges)));
	return ranges[precision];
}

static IVec2 getIntRange (glu::Precision precision)
{
	static const IVec2 ranges[] =
	{
		IVec2(-(1<< 7), (1<< 7)-1),
		IVec2(-(1<<15), (1<<15)-1),
		IVec2(0x80000000, 0x7fffffff)
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(ranges) == glu::PRECISION_LAST);
	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(ranges)));
	return ranges[precision];
}

static UVec2 getUintRange (glu::Precision precision)
{
	static const UVec2 ranges[] =
	{
		UVec2(0, (1<< 8)-1),
		UVec2(0, (1<<16)-1),
		UVec2(0, 0xffffffffu)
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(ranges) == glu::PRECISION_LAST);
	DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(ranges)));
	return ranges[precision];
}

static inline Vec4 readVec4 (const float* ptr, int numComponents)
{
	DE_ASSERT(numComponents >= 1);
	return Vec4(ptr[0],
				numComponents >= 2 ? ptr[1] : 0.0f,
				numComponents >= 3 ? ptr[2] : 0.0f,
				numComponents >= 4 ? ptr[3] : 0.0f);
}

static inline IVec4 readIVec4 (const int* ptr, int numComponents)
{
	DE_ASSERT(numComponents >= 1);
	return IVec4(ptr[0],
				 numComponents >= 2 ? ptr[1] : 0,
				 numComponents >= 3 ? ptr[2] : 0,
				 numComponents >= 4 ? ptr[3] : 0);
}

static void renderFloatReference (const tcu::PixelBufferAccess& dst, int gridWidth, int gridHeight, int numComponents, const float* vertices)
{
	const bool	isSRGB		= tcu::isSRGB(dst.getFormat());
	const float	cellW		= (float)dst.getWidth() / (float)(gridWidth-1);
	const float	cellH		= (float)dst.getHeight() / (float)(gridHeight-1);

	for (int y = 0; y < dst.getHeight(); y++)
	{
		for (int x = 0; x < dst.getWidth(); x++)
		{
			const int		cellX	= de::clamp(deFloorFloatToInt32((float)x / cellW), 0, gridWidth-2);
			const int		cellY	= de::clamp(deFloorFloatToInt32((float)y / cellH), 0, gridHeight-2);
			const float		xf		= ((float)x - (float)cellX*cellW + 0.5f) / cellW;
			const float		yf		= ((float)y - (float)cellY*cellH + 0.5f) / cellH;
			const Vec4		v00		= readVec4(vertices + ((cellY+0)*gridWidth + cellX+0)*numComponents, numComponents);
			const Vec4		v01		= readVec4(vertices + ((cellY+1)*gridWidth + cellX+0)*numComponents, numComponents);
			const Vec4		v10		= readVec4(vertices + ((cellY+0)*gridWidth + cellX+1)*numComponents, numComponents);
			const Vec4		v11		= readVec4(vertices + ((cellY+1)*gridWidth + cellX+1)*numComponents, numComponents);
			const bool		tri		= xf + yf >= 1.0f;
			const Vec4&		v0		= tri ? v11 : v00;
			const Vec4&		v1		= tri ? v01 : v10;
			const Vec4&		v2		= tri ? v10 : v01;
			const float		s		= tri ? 1.0f-xf : xf;
			const float		t		= tri ? 1.0f-yf : yf;
			const Vec4		color	= v0 + (v1-v0)*s + (v2-v0)*t;

			dst.setPixel(isSRGB ? tcu::linearToSRGB(color) : color, x, y);
		}
	}
}

static void renderIntReference (const tcu::PixelBufferAccess& dst, int gridWidth, int gridHeight, int numComponents, const int* vertices)
{
	float	cellW		= (float)dst.getWidth() / (float)(gridWidth-1);
	float	cellH		= (float)dst.getHeight() / (float)(gridHeight-1);

	for (int y = 0; y < dst.getHeight(); y++)
	{
		for (int x = 0; x < dst.getWidth(); x++)
		{
			int			cellX	= de::clamp(deFloorFloatToInt32((float)x / cellW), 0, gridWidth-2);
			int			cellY	= de::clamp(deFloorFloatToInt32((float)y / cellH), 0, gridHeight-2);
			IVec4		c		= readIVec4(vertices + (cellY*gridWidth + cellX+1)*numComponents, numComponents);

			dst.setPixel(c, x, y);
		}
	}
}

static const IVec4 s_swizzles[] =
{
	IVec4(0,1,2,3),
	IVec4(1,2,3,0),
	IVec4(2,3,0,1),
	IVec4(3,0,1,2),
	IVec4(3,2,1,0),
	IVec4(2,1,0,3),
	IVec4(1,0,3,2),
	IVec4(0,3,2,1)
};

template <typename T>
inline tcu::Vector<T, 4> swizzleVec (const tcu::Vector<T, 4>& vec, int swzNdx)
{
	const IVec4& swz = s_swizzles[swzNdx % DE_LENGTH_OF_ARRAY(s_swizzles)];
	return vec.swizzle(swz[0], swz[1], swz[2], swz[3]);
}

namespace
{

struct AttachmentData
{
	tcu::TextureFormat		format;					//!< Actual format of attachment.
	tcu::TextureFormat		referenceFormat;		//!< Used for reference rendering.
	tcu::TextureFormat		readFormat;
	int						numWrittenChannels;
	glu::Precision			outPrecision;
	vector<deUint8>			renderedData;
	vector<deUint8>			referenceData;
};

template<typename Type>
string valueRangeToString (int numValidChannels, const tcu::Vector<Type, 4>& minValue, const tcu::Vector<Type, 4>& maxValue)
{
	std::ostringstream stream;

	stream << "(";

	for (int i = 0; i < 4; i++)
	{
		if (i != 0)
			stream << ", ";

		if (i < numValidChannels)
			stream << minValue[i] << " -> " << maxValue[i];
		else
			stream << "Undef";
	}

	stream << ")";

	return stream.str();
}

void clearUndefined (const tcu::PixelBufferAccess& access, int numValidChannels)
{
	for (int y = 0; y < access.getHeight(); y++)
	for (int x = 0; x < access.getWidth(); x++)
	{
		switch (tcu::getTextureChannelClass(access.getFormat().type))
		{
			case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
			{
				const Vec4	srcPixel	= access.getPixel(x, y);
				Vec4		dstPixel	(0.0f, 0.0f, 0.0f, 1.0f);

				for (int channelNdx = 0; channelNdx < numValidChannels; channelNdx++)
					dstPixel[channelNdx] = srcPixel[channelNdx];

				access.setPixel(dstPixel, x, y);
				break;
			}

			case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
			case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
			case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
			case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
			{
				const IVec4	bitDepth	= tcu::getTextureFormatBitDepth(access.getFormat());
				const IVec4	srcPixel	= access.getPixelInt(x, y);
				IVec4		dstPixel	(0, 0, 0, (0x1u << (deUint64)bitDepth.w()) - 1);

				for (int channelNdx = 0; channelNdx < numValidChannels; channelNdx++)
					dstPixel[channelNdx] = srcPixel[channelNdx];

				access.setPixel(dstPixel, x, y);
				break;
			}

			default:
				DE_ASSERT(false);
		}
	}
}

} // anonymous

FragmentOutputCase::IterateResult FragmentOutputCase::iterate (void)
{
	TestLog&					log					= m_testCtx.getLog();
	const glw::Functions&		gl					= m_context.getRenderContext().getFunctions();

	// Compute grid size & index list.
	const int					minCellSize			= 8;
	const IVec2					minBufSize			= getMinSize(m_fboSpec);
	const int					gridWidth			= de::clamp(minBufSize.x()/minCellSize, 1, 255)+1;
	const int					gridHeight			= de::clamp(minBufSize.y()/minCellSize, 1, 255)+1;
	const int					numVertices			= gridWidth*gridHeight;
	const int					numQuads			= (gridWidth-1)*(gridHeight-1);
	const int					numIndices			= numQuads*6;

	const int					numInputVecs		= getNumInputVectors(m_outputs);
	vector<vector<deUint32> >	inputs				(numInputVecs);
	vector<float>				positions			(numVertices*4);
	vector<deUint16>			indices				(numIndices);

	const int					readAlignment		= 4;
	const int					viewportW			= minBufSize.x();
	const int					viewportH			= minBufSize.y();
	const int					numAttachments		= (int)m_fboSpec.size();

	vector<deUint32>			drawBuffers			(numAttachments);
	vector<AttachmentData>		attachments			(numAttachments);

	// Initialize attachment data.
	for (int ndx = 0; ndx < numAttachments; ndx++)
	{
		const tcu::TextureFormat		texFmt			= glu::mapGLInternalFormat(m_fboSpec[ndx].format);
		const tcu::TextureChannelClass	chnClass		= tcu::getTextureChannelClass(texFmt.type);
		const bool						isFixedPoint	= chnClass == tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT ||
														  chnClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;

		// \note Fixed-point formats use float reference to enable more accurate result verification.
		const tcu::TextureFormat		refFmt			= isFixedPoint ? tcu::TextureFormat(texFmt.order, tcu::TextureFormat::FLOAT) : texFmt;
		const tcu::TextureFormat		readFmt			= getFramebufferReadFormat(texFmt);
		const int						attachmentW		= m_fboSpec[ndx].width;
		const int						attachmentH		= m_fboSpec[ndx].height;

		drawBuffers[ndx]					= GL_COLOR_ATTACHMENT0+ndx;
		attachments[ndx].format				= texFmt;
		attachments[ndx].readFormat			= readFmt;
		attachments[ndx].referenceFormat	= refFmt;
		attachments[ndx].renderedData.resize(readFmt.getPixelSize()*attachmentW*attachmentH);
		attachments[ndx].referenceData.resize(refFmt.getPixelSize()*attachmentW*attachmentH);
	}

	// Initialize indices.
	for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
	{
		int	quadY	= quadNdx / (gridWidth-1);
		int quadX	= quadNdx - quadY*(gridWidth-1);

		indices[quadNdx*6+0] = quadX + quadY*gridWidth;
		indices[quadNdx*6+1] = quadX + (quadY+1)*gridWidth;
		indices[quadNdx*6+2] = quadX + quadY*gridWidth + 1;
		indices[quadNdx*6+3] = indices[quadNdx*6+1];
		indices[quadNdx*6+4] = quadX + (quadY+1)*gridWidth + 1;
		indices[quadNdx*6+5] = indices[quadNdx*6+2];
	}

	for (int y = 0; y < gridHeight; y++)
	{
		for (int x = 0; x < gridWidth; x++)
		{
			float	xf	= (float)x / (float)(gridWidth-1);
			float	yf	= (float)y / (float)(gridHeight-1);

			positions[(y*gridWidth + x)*4 + 0] = 2.0f*xf - 1.0f;
			positions[(y*gridWidth + x)*4 + 1] = 2.0f*yf - 1.0f;
			positions[(y*gridWidth + x)*4 + 2] = 0.0f;
			positions[(y*gridWidth + x)*4 + 3] = 1.0f;
		}
	}

	// Initialize input vectors.
	{
		int curInVec = 0;
		for (int outputNdx = 0; outputNdx < (int)m_outputs.size(); outputNdx++)
		{
			const FragmentOutput&	output		= m_outputs[outputNdx];
			bool					isFloat		= glu::isDataTypeFloatOrVec(output.type);
			bool					isInt		= glu::isDataTypeIntOrIVec(output.type);
			bool					isUint		= glu::isDataTypeUintOrUVec(output.type);
			int						numVecs		= output.arrayLength > 0 ? output.arrayLength : 1;
			int						numScalars	= glu::getDataTypeScalarSize(output.type);

			for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
			{
				inputs[curInVec].resize(numVertices*numScalars);

				// Record how many outputs are written in attachment.
				DE_ASSERT(output.location+vecNdx < (int)attachments.size());
				attachments[output.location+vecNdx].numWrittenChannels	= numScalars;
				attachments[output.location+vecNdx].outPrecision		= output.precision;

				if (isFloat)
				{
					Vec2		range	= getFloatRange(output.precision);
					Vec4		minVal	(range.x());
					Vec4		maxVal	(range.y());
					float*		dst		= (float*)&inputs[curInVec][0];

					if (de::inBounds(output.location+vecNdx, 0, (int)attachments.size()))
					{
						// \note Floating-point precision conversion is not well-defined. For that reason we must
						//       limit value range to intersection of both data type and render target value ranges.
						const tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(attachments[output.location+vecNdx].format);
						minVal = tcu::max(minVal, fmtInfo.valueMin);
						maxVal = tcu::min(maxVal, fmtInfo.valueMax);
					}

					m_testCtx.getLog() << TestLog::Message << "out" << curInVec << " value range: " << valueRangeToString(numScalars, minVal, maxVal) << TestLog::EndMessage;

					for (int y = 0; y < gridHeight; y++)
					{
						for (int x = 0; x < gridWidth; x++)
						{
							float	xf	= (float)x / (float)(gridWidth-1);
							float	yf	= (float)y / (float)(gridHeight-1);

							float	f0	= (xf + yf) * 0.5f;
							float	f1	= 0.5f + (xf - yf) * 0.5f;
							Vec4	f	= swizzleVec(Vec4(f0, f1, 1.0f-f0, 1.0f-f1), curInVec);
							Vec4	c	= minVal + (maxVal-minVal)*f;
							float*	v	= dst + (y*gridWidth + x)*numScalars;

							for (int ndx = 0; ndx < numScalars; ndx++)
								v[ndx] = c[ndx];
						}
					}
				}
				else if (isInt)
				{
					const IVec2	range	= getIntRange(output.precision);
					IVec4		minVal	(range.x());
					IVec4		maxVal	(range.y());

					if (de::inBounds(output.location+vecNdx, 0, (int)attachments.size()))
					{
						// Limit to range of output format as conversion mode is not specified.
						const IVec4 fmtBits		= tcu::getTextureFormatBitDepth(attachments[output.location+vecNdx].format);
						const BVec4	isZero		= lessThanEqual(fmtBits, IVec4(0));
						const IVec4	fmtMinVal	= (-(tcu::Vector<deInt64, 4>(1) << (fmtBits-1).cast<deInt64>())).asInt();
						const IVec4	fmtMaxVal	= ((tcu::Vector<deInt64, 4>(1) << (fmtBits-1).cast<deInt64>())-deInt64(1)).asInt();

						minVal = select(minVal, tcu::max(minVal, fmtMinVal), isZero);
						maxVal = select(maxVal, tcu::min(maxVal, fmtMaxVal), isZero);
					}

					m_testCtx.getLog() << TestLog::Message << "out" << curInVec << " value range: " << valueRangeToString(numScalars, minVal, maxVal) << TestLog::EndMessage;

					const IVec4	rangeDiv	= swizzleVec((IVec4(gridWidth, gridHeight, gridWidth, gridHeight)-1), curInVec);
					const IVec4	step		= ((maxVal.cast<deInt64>() - minVal.cast<deInt64>()) / (rangeDiv.cast<deInt64>())).asInt();
					deInt32*	dst			= (deInt32*)&inputs[curInVec][0];

					for (int y = 0; y < gridHeight; y++)
					{
						for (int x = 0; x < gridWidth; x++)
						{
							int			ix	= gridWidth - x - 1;
							int			iy	= gridHeight - y - 1;
							IVec4		c	= minVal + step*swizzleVec(IVec4(x, y, ix, iy), curInVec);
							deInt32*	v	= dst + (y*gridWidth + x)*numScalars;

							DE_ASSERT(boolAll(logicalAnd(greaterThanEqual(c, minVal), lessThanEqual(c, maxVal))));

							for (int ndx = 0; ndx < numScalars; ndx++)
								v[ndx] = c[ndx];
						}
					}
				}
				else if (isUint)
				{
					const UVec2	range	= getUintRange(output.precision);
					UVec4		maxVal	(range.y());

					if (de::inBounds(output.location+vecNdx, 0, (int)attachments.size()))
					{
						// Limit to range of output format as conversion mode is not specified.
						const IVec4	fmtBits		= tcu::getTextureFormatBitDepth(attachments[output.location+vecNdx].format);
						const UVec4	fmtMaxVal	= ((tcu::Vector<deUint64, 4>(1) << fmtBits.cast<deUint64>())-deUint64(1)).asUint();

						maxVal = tcu::min(maxVal, fmtMaxVal);
					}

					m_testCtx.getLog() << TestLog::Message << "out" << curInVec << " value range: "  << valueRangeToString(numScalars, UVec4(0), maxVal) << TestLog::EndMessage;

					const IVec4	rangeDiv	= swizzleVec((IVec4(gridWidth, gridHeight, gridWidth, gridHeight)-1), curInVec);
					const UVec4	step		= maxVal / rangeDiv.asUint();
					deUint32*	dst			= &inputs[curInVec][0];

					DE_ASSERT(range.x() == 0);

					for (int y = 0; y < gridHeight; y++)
					{
						for (int x = 0; x < gridWidth; x++)
						{
							int			ix	= gridWidth - x - 1;
							int			iy	= gridHeight - y - 1;
							UVec4		c	= step*swizzleVec(IVec4(x, y, ix, iy).asUint(), curInVec);
							deUint32*	v	= dst + (y*gridWidth + x)*numScalars;

							DE_ASSERT(boolAll(lessThanEqual(c, maxVal)));

							for (int ndx = 0; ndx < numScalars; ndx++)
								v[ndx] = c[ndx];
						}
					}
				}
				else
					DE_ASSERT(false);

				curInVec += 1;
			}
		}
	}

	// Render using gl.
	gl.useProgram(m_program->getProgram());
	gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
	gl.viewport(0, 0, viewportW, viewportH);
	gl.drawBuffers((int)drawBuffers.size(), &drawBuffers[0]);
	gl.disable(GL_DITHER); // Dithering causes issues with unorm formats. Those issues could be worked around in threshold, but it makes validation less accurate.
	GLU_EXPECT_NO_ERROR(gl.getError(), "After program setup");

	{
		int curInVec = 0;
		for (int outputNdx = 0; outputNdx < (int)m_outputs.size(); outputNdx++)
		{
			const FragmentOutput&	output			= m_outputs[outputNdx];
			bool					isArray			= output.arrayLength > 0;
			bool					isFloat			= glu::isDataTypeFloatOrVec(output.type);
			bool					isInt			= glu::isDataTypeIntOrIVec(output.type);
			bool					isUint			= glu::isDataTypeUintOrUVec(output.type);
			int						scalarSize		= glu::getDataTypeScalarSize(output.type);
			deUint32				glScalarType	= isFloat	? GL_FLOAT			:
													  isInt		? GL_INT			:
													  isUint	? GL_UNSIGNED_INT	: GL_NONE;
			int						numVecs			= isArray ? output.arrayLength : 1;

			for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
			{
				string	name	= string("in") + de::toString(outputNdx) + (isArray ? string("_") + de::toString(vecNdx) : string());
				int		loc		= gl.getAttribLocation(m_program->getProgram(), name.c_str());

				if (loc >= 0)
				{
					gl.enableVertexAttribArray(loc);
					if (isFloat)
						gl.vertexAttribPointer(loc, scalarSize, glScalarType, GL_FALSE, 0, &inputs[curInVec][0]);
					else
						gl.vertexAttribIPointer(loc, scalarSize, glScalarType, 0, &inputs[curInVec][0]);
				}
				else
					log << TestLog::Message << "Warning: No location for attribute '" << name << "' found." << TestLog::EndMessage;

				curInVec += 1;
			}
		}
	}
	{
		int posLoc = gl.getAttribLocation(m_program->getProgram(), "a_position");
		TCU_CHECK(posLoc >= 0);
		gl.enableVertexAttribArray(posLoc);
		gl.vertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &positions[0]);
	}
	GLU_EXPECT_NO_ERROR(gl.getError(), "After attribute setup");

	gl.drawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, &indices[0]);
	GLU_EXPECT_NO_ERROR(gl.getError(), "glDrawElements");

	// Read all attachment points.
	for (int ndx = 0; ndx < numAttachments; ndx++)
	{
		const glu::TransferFormat		transferFmt			= glu::getTransferFormat(attachments[ndx].readFormat);
		void*							dst					= &attachments[ndx].renderedData[0];
		const int						attachmentW			= m_fboSpec[ndx].width;
		const int						attachmentH			= m_fboSpec[ndx].height;
		const int						numValidChannels	= attachments[ndx].numWrittenChannels;
		const tcu::PixelBufferAccess	rendered			(attachments[ndx].readFormat, attachmentW, attachmentH, 1, deAlign32(attachments[ndx].readFormat.getPixelSize()*attachmentW, readAlignment), 0, &attachments[ndx].renderedData[0]);

		gl.readBuffer(GL_COLOR_ATTACHMENT0+ndx);
		gl.readPixels(0, 0, minBufSize.x(), minBufSize.y(), transferFmt.format, transferFmt.dataType, dst);

		clearUndefined(rendered, numValidChannels);
	}

	// Render reference images.
	{
		int curInNdx = 0;
		for (int outputNdx = 0; outputNdx < (int)m_outputs.size(); outputNdx++)
		{
			const FragmentOutput&	output			= m_outputs[outputNdx];
			const bool				isArray			= output.arrayLength > 0;
			const bool				isFloat			= glu::isDataTypeFloatOrVec(output.type);
			const bool				isInt			= glu::isDataTypeIntOrIVec(output.type);
			const bool				isUint			= glu::isDataTypeUintOrUVec(output.type);
			const int				scalarSize		= glu::getDataTypeScalarSize(output.type);
			const int				numVecs			= isArray ? output.arrayLength : 1;

			for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
			{
				const int		location	= output.location+vecNdx;
				const void*		inputData	= &inputs[curInNdx][0];

				DE_ASSERT(de::inBounds(location, 0, (int)m_fboSpec.size()));

				const int						bufW			= m_fboSpec[location].width;
				const int						bufH			= m_fboSpec[location].height;
				const tcu::PixelBufferAccess	buf				(attachments[location].referenceFormat, bufW, bufH, 1, &attachments[location].referenceData[0]);
				const tcu::PixelBufferAccess	viewportBuf		= getSubregion(buf, 0, 0, 0, viewportW, viewportH, 1);

				if (isInt || isUint)
					renderIntReference(viewportBuf, gridWidth, gridHeight, scalarSize, (const int*)inputData);
				else if (isFloat)
					renderFloatReference(viewportBuf, gridWidth, gridHeight, scalarSize, (const float*)inputData);
				else
					DE_ASSERT(false);

				curInNdx += 1;
			}
		}
	}

	// Compare all images.
	bool allLevelsOk = true;
	for (int attachNdx = 0; attachNdx < numAttachments; attachNdx++)
	{
		const int						attachmentW			= m_fboSpec[attachNdx].width;
		const int						attachmentH			= m_fboSpec[attachNdx].height;
		const int						numValidChannels	= attachments[attachNdx].numWrittenChannels;
		const tcu::BVec4				cmpMask				(numValidChannels >= 1, numValidChannels >= 2, numValidChannels >= 3, numValidChannels >= 4);
		const glu::Precision			outPrecision		= attachments[attachNdx].outPrecision;
		const tcu::TextureFormat&		format				= attachments[attachNdx].format;
		tcu::ConstPixelBufferAccess		rendered			(attachments[attachNdx].readFormat, attachmentW, attachmentH, 1, deAlign32(attachments[attachNdx].readFormat.getPixelSize()*attachmentW, readAlignment), 0, &attachments[attachNdx].renderedData[0]);
		tcu::ConstPixelBufferAccess		reference			(attachments[attachNdx].referenceFormat, attachmentW, attachmentH, 1, &attachments[attachNdx].referenceData[0]);
		tcu::TextureChannelClass		texClass			= tcu::getTextureChannelClass(format.type);
		bool							isOk				= true;
		const string					name				= string("Attachment") + de::toString(attachNdx);
		const string					desc				= string("Color attachment ") + de::toString(attachNdx);

		log << TestLog::Message << "Attachment " << attachNdx << ": " << numValidChannels << " channels have defined values and used for comparison" << TestLog::EndMessage;

		switch (texClass)
		{
			case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
			{
				const deUint32	interpThreshold		= 4;	//!< 4 ULP interpolation threshold (interpolation always in highp)
				deUint32		outTypeThreshold	= 0;	//!< Threshold based on output type
				UVec4			formatThreshold;			//!< Threshold computed based on format.
				UVec4			finalThreshold;

				// 1 ULP rounding error is allowed for smaller floating-point formats
				switch (format.type)
				{
					case tcu::TextureFormat::FLOAT:							formatThreshold = UVec4(0);											break;
					case tcu::TextureFormat::HALF_FLOAT:					formatThreshold = UVec4((1<<(23-10)));								break;
					case tcu::TextureFormat::UNSIGNED_INT_11F_11F_10F_REV:	formatThreshold = UVec4((1<<(23-6)), (1<<(23-6)), (1<<(23-5)), 0);	break;
					default:
						DE_ASSERT(false);
						break;
				}

				// 1 ULP rounding error for highp -> output precision cast
				switch (outPrecision)
				{
					case glu::PRECISION_LOWP:		outTypeThreshold	= (1<<(23-8));	break;
					case glu::PRECISION_MEDIUMP:	outTypeThreshold	= (1<<(23-10));	break;
					case glu::PRECISION_HIGHP:		outTypeThreshold	= 0;			break;
					default:
						DE_ASSERT(false);
				}

				finalThreshold = select(max(formatThreshold, UVec4(deMax32(interpThreshold, outTypeThreshold))), UVec4(~0u), cmpMask);

				isOk = tcu::floatUlpThresholdCompare(log, name.c_str(), desc.c_str(), reference, rendered, finalThreshold, tcu::COMPARE_LOG_RESULT);
				break;
			}

			case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
			{
				// \note glReadPixels() allows only 8 bits to be read. This means that RGB10_A2 will loose some
				// bits in the process and it must be taken into account when computing threshold.
				const IVec4		bits			= min(IVec4(8), tcu::getTextureFormatBitDepth(format));
				const Vec4		baseThreshold	= 1.0f / ((IVec4(1) << bits)-1).asFloat();
				const Vec4		threshold		= select(baseThreshold, Vec4(2.0f), cmpMask);

				isOk = tcu::floatThresholdCompare(log, name.c_str(), desc.c_str(), reference, rendered, threshold, tcu::COMPARE_LOG_RESULT);
				break;
			}

			case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
			case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
			{
				const tcu::UVec4 threshold = select(UVec4(0u), UVec4(~0u), cmpMask);
				isOk = tcu::intThresholdCompare(log, name.c_str(), desc.c_str(), reference, rendered, threshold, tcu::COMPARE_LOG_RESULT);
				break;
			}

			default:
				TCU_FAIL("Unsupported comparison");
				break;
		}

		if (!isOk)
			allLevelsOk = false;
	}

	m_testCtx.setTestResult(allLevelsOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
							allLevelsOk ? "Pass"				: "Image comparison failed");
	return STOP;
}

FragmentOutputTests::FragmentOutputTests (Context& context)
	: TestCaseGroup(context, "fragment_out", "Fragment output tests")
{
}

FragmentOutputTests::~FragmentOutputTests (void)
{
}

static FragmentOutputCase* createRandomCase (Context& context, int minRenderTargets, int maxRenderTargets, deUint32 seed)
{
	static const glu::DataType outputTypes[] =
	{
		glu::TYPE_FLOAT,
		glu::TYPE_FLOAT_VEC2,
		glu::TYPE_FLOAT_VEC3,
		glu::TYPE_FLOAT_VEC4,
		glu::TYPE_INT,
		glu::TYPE_INT_VEC2,
		glu::TYPE_INT_VEC3,
		glu::TYPE_INT_VEC4,
		glu::TYPE_UINT,
		glu::TYPE_UINT_VEC2,
		glu::TYPE_UINT_VEC3,
		glu::TYPE_UINT_VEC4
	};
	static const glu::Precision precisions[] =
	{
		glu::PRECISION_LOWP,
		glu::PRECISION_MEDIUMP,
		glu::PRECISION_HIGHP
	};
	static const deUint32 floatFormats[] =
	{
		GL_RGBA32F,
		GL_RGBA16F,
		GL_R11F_G11F_B10F,
		GL_RG32F,
		GL_RG16F,
		GL_R32F,
		GL_R16F,
		GL_RGBA8,
		GL_SRGB8_ALPHA8,
		GL_RGB10_A2,
		GL_RGBA4,
		GL_RGB5_A1,
		GL_RGB8,
		GL_RGB565,
		GL_RG8,
		GL_R8
	};
	static const deUint32 intFormats[] =
	{
		GL_RGBA32I,
		GL_RGBA16I,
		GL_RGBA8I,
		GL_RG32I,
		GL_RG16I,
		GL_RG8I,
		GL_R32I,
		GL_R16I,
		GL_R8I
	};
	static const deUint32 uintFormats[] =
	{
		GL_RGBA32UI,
		GL_RGBA16UI,
		GL_RGBA8UI,
		GL_RGB10_A2UI,
		GL_RG32UI,
		GL_RG16UI,
		GL_RG8UI,
		GL_R32UI,
		GL_R16UI,
		GL_R8UI
	};

	de::Random					rnd			(seed);
	vector<FragmentOutput>		outputs;
	vector<BufferSpec>			targets;
	vector<glu::DataType>		outTypes;

	int							numTargets	= rnd.getInt(minRenderTargets, maxRenderTargets);
	const int					width		= 128; // \todo [2012-04-10 pyry] Separate randomized sizes per target?
	const int					height		= 64;
	const int					samples		= 0;

	// Compute outputs.
	int curLoc = 0;
	while (curLoc < numTargets)
	{
		bool			useArray		= rnd.getFloat() < 0.3f;
		int				maxArrayLen		= numTargets-curLoc;
		int				arrayLen		= useArray ? rnd.getInt(1, maxArrayLen) : 0;
		glu::DataType	basicType		= rnd.choose<glu::DataType>(&outputTypes[0], &outputTypes[0] + DE_LENGTH_OF_ARRAY(outputTypes));
		glu::Precision	precision		= rnd.choose<glu::Precision>(&precisions[0], &precisions[0] + DE_LENGTH_OF_ARRAY(precisions));
		int				numLocations	= useArray ? arrayLen : 1;

		outputs.push_back(FragmentOutput(basicType, precision, curLoc, arrayLen));

		for (int ndx = 0; ndx < numLocations; ndx++)
			outTypes.push_back(basicType);

		curLoc += numLocations;
	}
	DE_ASSERT(curLoc == numTargets);
	DE_ASSERT((int)outTypes.size() == numTargets);

	// Compute buffers.
	while ((int)targets.size() < numTargets)
	{
		glu::DataType	outType		= outTypes[targets.size()];
		bool			isFloat		= glu::isDataTypeFloatOrVec(outType);
		bool			isInt		= glu::isDataTypeIntOrIVec(outType);
		bool			isUint		= glu::isDataTypeUintOrUVec(outType);
		deUint32		format		= 0;

		if (isFloat)
			format = rnd.choose<deUint32>(&floatFormats[0], &floatFormats[0] + DE_LENGTH_OF_ARRAY(floatFormats));
		else if (isInt)
			format = rnd.choose<deUint32>(&intFormats[0], &intFormats[0] + DE_LENGTH_OF_ARRAY(intFormats));
		else if (isUint)
			format = rnd.choose<deUint32>(&uintFormats[0], &uintFormats[0] + DE_LENGTH_OF_ARRAY(uintFormats));
		else
			DE_ASSERT(false);

		targets.push_back(BufferSpec(format, width, height, samples));
	}

	return new FragmentOutputCase(context, de::toString(seed).c_str(), "", targets, outputs);
}

void FragmentOutputTests::init (void)
{
	static const deUint32 requiredFloatFormats[] =
	{
		GL_RGBA32F,
		GL_RGBA16F,
		GL_R11F_G11F_B10F,
		GL_RG32F,
		GL_RG16F,
		GL_R32F,
		GL_R16F
	};
	static const deUint32 requiredFixedFormats[] =
	{
		GL_RGBA8,
		GL_SRGB8_ALPHA8,
		GL_RGB10_A2,
		GL_RGBA4,
		GL_RGB5_A1,
		GL_RGB8,
		GL_RGB565,
		GL_RG8,
		GL_R8
	};
	static const deUint32 requiredIntFormats[] =
	{
		GL_RGBA32I,
		GL_RGBA16I,
		GL_RGBA8I,
		GL_RG32I,
		GL_RG16I,
		GL_RG8I,
		GL_R32I,
		GL_R16I,
		GL_R8I
	};
	static const deUint32 requiredUintFormats[] =
	{
		GL_RGBA32UI,
		GL_RGBA16UI,
		GL_RGBA8UI,
		GL_RGB10_A2UI,
		GL_RG32UI,
		GL_RG16UI,
		GL_RG8UI,
		GL_R32UI,
		GL_R16UI,
		GL_R8UI
	};

	static const glu::Precision precisions[] =
	{
		glu::PRECISION_LOWP,
		glu::PRECISION_MEDIUMP,
		glu::PRECISION_HIGHP
	};

	// .basic.
	{
		tcu::TestCaseGroup* basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic", "Basic fragment output tests");
		addChild(basicGroup);

		const int	width	= 64;
		const int	height	= 64;
		const int	samples	= 0;

		// .float
		tcu::TestCaseGroup* floatGroup = new tcu::TestCaseGroup(m_testCtx, "float", "Floating-point output tests");
		basicGroup->addChild(floatGroup);
		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFloatFormats); fmtNdx++)
		{
			deUint32			format		= requiredFloatFormats[fmtNdx];
			string				fmtName		= getFormatName(format);
			vector<BufferSpec>	fboSpec;

			fboSpec.push_back(BufferSpec(format, width, height, samples));

			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
			{
				glu::Precision	prec		= precisions[precNdx];
				string			precName	= glu::getPrecisionName(prec);

				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_float").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT,		prec, 0)).toVec()));
				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2,	prec, 0)).toVec()));
				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3,	prec, 0)).toVec()));
				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4,	prec, 0)).toVec()));
			}
		}

		// .fixed
		tcu::TestCaseGroup* fixedGroup = new tcu::TestCaseGroup(m_testCtx, "fixed", "Fixed-point output tests");
		basicGroup->addChild(fixedGroup);
		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFixedFormats); fmtNdx++)
		{
			deUint32			format		= requiredFixedFormats[fmtNdx];
			string				fmtName		= getFormatName(format);
			vector<BufferSpec>	fboSpec;

			fboSpec.push_back(BufferSpec(format, width, height, samples));

			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
			{
				glu::Precision	prec		= precisions[precNdx];
				string			precName	= glu::getPrecisionName(prec);

				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_float").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT,		prec, 0)).toVec()));
				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2,	prec, 0)).toVec()));
				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3,	prec, 0)).toVec()));
				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4,	prec, 0)).toVec()));
			}
		}

		// .int
		tcu::TestCaseGroup* intGroup = new tcu::TestCaseGroup(m_testCtx, "int", "Integer output tests");
		basicGroup->addChild(intGroup);
		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredIntFormats); fmtNdx++)
		{
			deUint32			format		= requiredIntFormats[fmtNdx];
			string				fmtName		= getFormatName(format);
			vector<BufferSpec>	fboSpec;

			fboSpec.push_back(BufferSpec(format, width, height, samples));

			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
			{
				glu::Precision	prec		= precisions[precNdx];
				string			precName	= glu::getPrecisionName(prec);

				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_int").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT,		prec, 0)).toVec()));
				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC2,	prec, 0)).toVec()));
				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC3,	prec, 0)).toVec()));
				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC4,	prec, 0)).toVec()));
			}
		}

		// .uint
		tcu::TestCaseGroup* uintGroup = new tcu::TestCaseGroup(m_testCtx, "uint", "Usigned integer output tests");
		basicGroup->addChild(uintGroup);
		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredUintFormats); fmtNdx++)
		{
			deUint32			format		= requiredUintFormats[fmtNdx];
			string				fmtName		= getFormatName(format);
			vector<BufferSpec>	fboSpec;

			fboSpec.push_back(BufferSpec(format, width, height, samples));

			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
			{
				glu::Precision	prec		= precisions[precNdx];
				string			precName	= glu::getPrecisionName(prec);

				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uint").c_str(),		"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT,			prec, 0)).toVec()));
				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC2,	prec, 0)).toVec()));
				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC3,	prec, 0)).toVec()));
				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC4,	prec, 0)).toVec()));
			}
		}
	}

	// .array
	{
		tcu::TestCaseGroup* arrayGroup = new tcu::TestCaseGroup(m_testCtx, "array", "Array outputs");
		addChild(arrayGroup);

		const int	width		= 64;
		const int	height		= 64;
		const int	samples		= 0;
		const int	numTargets	= 3;

		// .float
		tcu::TestCaseGroup* floatGroup = new tcu::TestCaseGroup(m_testCtx, "float", "Floating-point output tests");
		arrayGroup->addChild(floatGroup);
		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFloatFormats); fmtNdx++)
		{
			deUint32			format		= requiredFloatFormats[fmtNdx];
			string				fmtName		= getFormatName(format);
			vector<BufferSpec>	fboSpec;

			for (int ndx = 0; ndx < numTargets; ndx++)
				fboSpec.push_back(BufferSpec(format, width, height, samples));

			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
			{
				glu::Precision	prec		= precisions[precNdx];
				string			precName	= glu::getPrecisionName(prec);

				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_float").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT,		prec, 0, numTargets)).toVec()));
				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2,	prec, 0, numTargets)).toVec()));
				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3,	prec, 0, numTargets)).toVec()));
				floatGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4,	prec, 0, numTargets)).toVec()));
			}
		}

		// .fixed
		tcu::TestCaseGroup* fixedGroup = new tcu::TestCaseGroup(m_testCtx, "fixed", "Fixed-point output tests");
		arrayGroup->addChild(fixedGroup);
		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFixedFormats); fmtNdx++)
		{
			deUint32			format		= requiredFixedFormats[fmtNdx];
			string				fmtName		= getFormatName(format);
			vector<BufferSpec>	fboSpec;

			for (int ndx = 0; ndx < numTargets; ndx++)
				fboSpec.push_back(BufferSpec(format, width, height, samples));

			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
			{
				glu::Precision	prec		= precisions[precNdx];
				string			precName	= glu::getPrecisionName(prec);

				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_float").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT,		prec, 0, numTargets)).toVec()));
				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2,	prec, 0, numTargets)).toVec()));
				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3,	prec, 0, numTargets)).toVec()));
				fixedGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4,	prec, 0, numTargets)).toVec()));
			}
		}

		// .int
		tcu::TestCaseGroup* intGroup = new tcu::TestCaseGroup(m_testCtx, "int", "Integer output tests");
		arrayGroup->addChild(intGroup);
		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredIntFormats); fmtNdx++)
		{
			deUint32			format		= requiredIntFormats[fmtNdx];
			string				fmtName		= getFormatName(format);
			vector<BufferSpec>	fboSpec;

			for (int ndx = 0; ndx < numTargets; ndx++)
				fboSpec.push_back(BufferSpec(format, width, height, samples));

			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
			{
				glu::Precision	prec		= precisions[precNdx];
				string			precName	= glu::getPrecisionName(prec);

				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_int").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT,		prec, 0, numTargets)).toVec()));
				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC2,	prec, 0, numTargets)).toVec()));
				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC3,	prec, 0, numTargets)).toVec()));
				intGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC4,	prec, 0, numTargets)).toVec()));
			}
		}

		// .uint
		tcu::TestCaseGroup* uintGroup = new tcu::TestCaseGroup(m_testCtx, "uint", "Usigned integer output tests");
		arrayGroup->addChild(uintGroup);
		for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredUintFormats); fmtNdx++)
		{
			deUint32			format		= requiredUintFormats[fmtNdx];
			string				fmtName		= getFormatName(format);
			vector<BufferSpec>	fboSpec;

			for (int ndx = 0; ndx < numTargets; ndx++)
				fboSpec.push_back(BufferSpec(format, width, height, samples));

			for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
			{
				glu::Precision	prec		= precisions[precNdx];
				string			precName	= glu::getPrecisionName(prec);

				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uint").c_str(),		"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT,			prec, 0, numTargets)).toVec()));
				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec2").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC2,	prec, 0, numTargets)).toVec()));
				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec3").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC3,	prec, 0, numTargets)).toVec()));
				uintGroup->addChild(new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec4").c_str(),	"",	fboSpec, (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC4,	prec, 0, numTargets)).toVec()));
			}
		}
	}

	// .random
	{
		tcu::TestCaseGroup* randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Random fragment output cases");
		addChild(randomGroup);

		for (deUint32 seed = 0; seed < 100; seed++)
			randomGroup->addChild(createRandomCase(m_context, 2, 4, seed));
	}
}

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