/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) 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 Vertex array and buffer tests
 *//*--------------------------------------------------------------------*/

#include "glsVertexArrayTests.hpp"

#include "deRandom.h"

#include "tcuTestLog.hpp"
#include "tcuPixelFormat.hpp"
#include "tcuRGBA.hpp"
#include "tcuSurface.hpp"
#include "tcuVector.hpp"
#include "tcuTestLog.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuImageCompare.hpp"

#include "gluPixelTransfer.hpp"
#include "gluCallLogWrapper.hpp"

#include "sglrContext.hpp"
#include "sglrReferenceContext.hpp"
#include "sglrGLContext.hpp"

#include "deMath.h"
#include "deStringUtil.hpp"

#include <cstring>
#include <cmath>
#include <vector>
#include <sstream>
#include <limits>
#include <algorithm>

#include "glwDefs.hpp"
#include "glwEnums.hpp"

namespace deqp
{
namespace gls
{

using tcu::TestLog;
using namespace glw; // GL types

std::string Array::targetToString(Target target)
{
	DE_ASSERT(target < TARGET_LAST);

	static const char* targets[] =
	{
		"element_array",	// TARGET_ELEMENT_ARRAY = 0,
		"array"				// TARGET_ARRAY,
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(targets) == Array::TARGET_LAST);

	return targets[(int)target];
}

std::string Array::inputTypeToString(InputType type)
{
	DE_ASSERT(type < INPUTTYPE_LAST);

	static const char* types[] =
	{
		"float",			// INPUTTYPE_FLOAT = 0,
		"fixed",			// INPUTTYPE_FIXED,
		"double",			// INPUTTYPE_DOUBLE

		"byte",				// INPUTTYPE_BYTE,
		"short",			// INPUTTYPE_SHORT,

		"unsigned_byte",	// INPUTTYPE_UNSIGNED_BYTE,
		"unsigned_short",	// INPUTTYPE_UNSIGNED_SHORT,

		"int",						// INPUTTYPE_INT,
		"unsigned_int",				// INPUTTYPE_UNSIGNED_INT,
		"half",						// INPUTTYPE_HALF,
		"usigned_int2_10_10_10",	// INPUTTYPE_UNSIGNED_INT_2_10_10_10,
		"int2_10_10_10"				// INPUTTYPE_INT_2_10_10_10,
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == Array::INPUTTYPE_LAST);

	return types[(int)type];
}

std::string Array::outputTypeToString(OutputType type)
{
	DE_ASSERT(type < OUTPUTTYPE_LAST);

	static const char* types[] =
	{
		"float",		// OUTPUTTYPE_FLOAT = 0,
		"vec2",			// OUTPUTTYPE_VEC2,
		"vec3",			// OUTPUTTYPE_VEC3,
		"vec4",			// OUTPUTTYPE_VEC4,

		"int",			// OUTPUTTYPE_INT,
		"uint",			// OUTPUTTYPE_UINT,

		"ivec2",		// OUTPUTTYPE_IVEC2,
		"ivec3",		// OUTPUTTYPE_IVEC3,
		"ivec4",		// OUTPUTTYPE_IVEC4,

		"uvec2",		// OUTPUTTYPE_UVEC2,
		"uvec3",		// OUTPUTTYPE_UVEC3,
		"uvec4",		// OUTPUTTYPE_UVEC4,
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == Array::OUTPUTTYPE_LAST);

	return types[(int)type];
}

std::string Array::usageTypeToString(Usage usage)
{
	DE_ASSERT(usage < USAGE_LAST);

	static const char* usages[] =
	{
		"dynamic_draw",	// USAGE_DYNAMIC_DRAW = 0,
		"static_draw",	// USAGE_STATIC_DRAW,
		"stream_draw",	// USAGE_STREAM_DRAW,

		"stream_read",	// USAGE_STREAM_READ,
		"stream_copy",	// USAGE_STREAM_COPY,

		"static_read",	// USAGE_STATIC_READ,
		"static_copy",	// USAGE_STATIC_COPY,

		"dynamic_read",	// USAGE_DYNAMIC_READ,
		"dynamic_copy",	// USAGE_DYNAMIC_COPY,
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(usages) == Array::USAGE_LAST);

	return usages[(int)usage];
}

std::string	Array::storageToString (Storage storage)
{
	DE_ASSERT(storage < STORAGE_LAST);

	static const char* storages[] =
	{
		"user_ptr",	// STORAGE_USER = 0,
		"buffer"	// STORAGE_BUFFER,
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(storages) == Array::STORAGE_LAST);

	return storages[(int)storage];
}

std::string Array::primitiveToString (Primitive primitive)
{
	DE_ASSERT(primitive < PRIMITIVE_LAST);

	static const char* primitives[] =
	{
		"points",			// PRIMITIVE_POINTS ,
		"triangles",		// PRIMITIVE_TRIANGLES,
		"triangle_fan",		// PRIMITIVE_TRIANGLE_FAN,
		"triangle_strip"	// PRIMITIVE_TRIANGLE_STRIP,
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(primitives) == Array::PRIMITIVE_LAST);

	return primitives[(int)primitive];
}

int Array::inputTypeSize (InputType type)
{
	DE_ASSERT(type < INPUTTYPE_LAST);

	static const int size[] =
	{
		sizeof(float),		// INPUTTYPE_FLOAT = 0,
		sizeof(deInt32),	// INPUTTYPE_FIXED,
		sizeof(double),		// INPUTTYPE_DOUBLE

		sizeof(deInt8),		// INPUTTYPE_BYTE,
		sizeof(deInt16),	// INPUTTYPE_SHORT,

		sizeof(deUint8),	// INPUTTYPE_UNSIGNED_BYTE,
		sizeof(deUint16),	// INPUTTYPE_UNSIGNED_SHORT,

		sizeof(deInt32),		// INPUTTYPE_INT,
		sizeof(deUint32),		// INPUTTYPE_UNSIGNED_INT,
		sizeof(deFloat16),		// INPUTTYPE_HALF,
		sizeof(deUint32) / 4,		// INPUTTYPE_UNSIGNED_INT_2_10_10_10,
		sizeof(deUint32) / 4		// INPUTTYPE_INT_2_10_10_10,
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(size) == Array::INPUTTYPE_LAST);

	return size[(int)type];
}

static bool inputTypeIsFloatType (Array::InputType type)
{
	if (type == Array::INPUTTYPE_FLOAT)
		return true;
	if (type == Array::INPUTTYPE_FIXED)
		return true;
	if (type == Array::INPUTTYPE_DOUBLE)
		return true;
	if (type == Array::INPUTTYPE_HALF)
		return true;
	return false;
}

static bool outputTypeIsFloatType (Array::OutputType type)
{
	if (type == Array::OUTPUTTYPE_FLOAT
		|| type == Array::OUTPUTTYPE_VEC2
		|| type == Array::OUTPUTTYPE_VEC3
		|| type == Array::OUTPUTTYPE_VEC4)
		return true;

	return false;
}

template<class T>
inline T getRandom (deRandom& rnd, T min, T max);

template<>
inline GLValue::Float getRandom (deRandom& rnd, GLValue::Float min, GLValue::Float max)
{
	if (max < min)
		return min;

	return GLValue::Float::create(min + deRandom_getFloat(&rnd) * (max.to<float>() - min.to<float>()));
}

template<>
inline GLValue::Short getRandom (deRandom& rnd, GLValue::Short min, GLValue::Short max)
{
	if (max < min)
		return min;

	return GLValue::Short::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
}

template<>
inline GLValue::Ushort getRandom (deRandom& rnd, GLValue::Ushort min, GLValue::Ushort max)
{
	if (max < min)
		return min;

	return GLValue::Ushort::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
}

template<>
inline GLValue::Byte getRandom (deRandom& rnd, GLValue::Byte min, GLValue::Byte max)
{
	if (max < min)
		return min;

	return GLValue::Byte::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
}

template<>
inline GLValue::Ubyte getRandom (deRandom& rnd, GLValue::Ubyte min, GLValue::Ubyte max)
{
	if (max < min)
		return min;

	return GLValue::Ubyte::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>()))));
}

template<>
inline GLValue::Fixed getRandom (deRandom& rnd, GLValue::Fixed min, GLValue::Fixed max)
{
	if (max < min)
		return min;

	return GLValue::Fixed::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
}

template<>
inline GLValue::Half getRandom (deRandom& rnd, GLValue::Half min, GLValue::Half max)
{
	if (max < min)
		return min;

	float fMax = max.to<float>();
	float fMin = min.to<float>();
	GLValue::Half h = GLValue::Half::create(fMin + deRandom_getFloat(&rnd) * (fMax - fMin));
	return h;
}

template<>
inline GLValue::Int getRandom (deRandom& rnd, GLValue::Int min, GLValue::Int max)
{
	if (max < min)
		return min;

	return GLValue::Int::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
}

template<>
inline GLValue::Uint getRandom (deRandom& rnd, GLValue::Uint min, GLValue::Uint max)
{
	if (max < min)
		return min;

	return GLValue::Uint::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
}

template<>
inline GLValue::Double getRandom (deRandom& rnd, GLValue::Double min, GLValue::Double max)
{
	if (max < min)
		return min;

	return GLValue::Double::create(min + deRandom_getFloat(&rnd) * (max.to<float>() - min.to<float>()));
}

// Minimum difference required between coordinates
template<class T>
inline T minValue (void);

template<>
inline GLValue::Float minValue (void)
{
	return GLValue::Float::create(4 * 1.0f);
}

template<>
inline GLValue::Short minValue (void)
{
	return GLValue::Short::create(4 * 256);
}

template<>
inline GLValue::Ushort minValue (void)
{
	return GLValue::Ushort::create(4 * 256);
}

template<>
inline GLValue::Byte minValue (void)
{
	return GLValue::Byte::create(4 * 1);
}

template<>
inline GLValue::Ubyte minValue (void)
{
	return GLValue::Ubyte::create(4 * 2);
}

template<>
inline GLValue::Fixed minValue (void)
{
	return GLValue::Fixed::create(4 * 512);
}

template<>
inline GLValue::Int minValue (void)
{
	return GLValue::Int::create(4 * 16777216);
}

template<>
inline GLValue::Uint minValue (void)
{
	return GLValue::Uint::create(4 * 16777216);
}

template<>
inline GLValue::Half minValue (void)
{
	return GLValue::Half::create(4 * 1.0f);
}

template<>
inline GLValue::Double minValue (void)
{
	return GLValue::Double::create(4 * 1.0f);
}

template<class T>
inline T abs (T val);

template<>
inline GLValue::Fixed abs (GLValue::Fixed val)
{
	return GLValue::Fixed::create(0x7FFFu & val.getValue());
}

template<>
inline GLValue::Ubyte abs (GLValue::Ubyte val)
{
	return val;
}

template<>
inline GLValue::Byte abs (GLValue::Byte val)
{
	return GLValue::Byte::create(0x7Fu & val.getValue());
}

template<>
inline GLValue::Ushort abs (GLValue::Ushort val)
{
	return val;
}

template<>
inline GLValue::Short abs (GLValue::Short val)
{
	return GLValue::Short::create(0x7FFFu & val.getValue());
}

template<>
inline GLValue::Float abs (GLValue::Float val)
{
	return GLValue::Float::create(std::fabs(val.to<float>()));
}

template<>
inline GLValue::Uint abs (GLValue::Uint val)
{
	return val;
}

template<>
inline GLValue::Int abs (GLValue::Int val)
{
	return GLValue::Int::create(0x7FFFFFFFu & val.getValue());
}

template<>
inline GLValue::Half abs (GLValue::Half val)
{
	return GLValue::Half::create(std::fabs(val.to<float>()));
}

template<>
inline GLValue::Double abs (GLValue::Double val)
{
	return GLValue::Double::create(std::fabs(val.to<float>()));
}

template<class T>
inline static void alignmentSafeAssignment (char* dst, T val)
{
	std::memcpy(dst, &val, sizeof(T));
}

ContextArray::ContextArray (Storage storage, sglr::Context& context)
	: m_storage			(storage)
	, m_ctx				(context)
	, m_glBuffer		(0)
	, m_bound			(false)
	, m_attribNdx		(0)
	, m_size			(0)
	, m_data			(DE_NULL)
	, m_componentCount	(1)
	, m_target			(Array::TARGET_ARRAY)
	, m_inputType		(Array::INPUTTYPE_FLOAT)
	, m_outputType		(Array::OUTPUTTYPE_VEC4)
	, m_normalize		(false)
	, m_stride			(0)
	, m_offset			(0)
{
	if (m_storage == STORAGE_BUFFER)
	{
		m_ctx.genBuffers(1, &m_glBuffer);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glGenBuffers()");
	}
}

ContextArray::~ContextArray	(void)
{
	if (m_storage == STORAGE_BUFFER)
	{
		m_ctx.deleteBuffers(1, &m_glBuffer);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDeleteBuffers()");
	}
	else if (m_storage == STORAGE_USER)
		delete[] m_data;
	else
		DE_ASSERT(false);
}

Array* ContextArrayPack::getArray (int i)
{
	return m_arrays.at(i);
}

void ContextArray::data (Target target, int size, const char* ptr, Usage usage)
{
	m_size = size;
	m_target = target;

	if (m_storage == STORAGE_BUFFER)
	{
		m_ctx.bindBuffer(targetToGL(target), m_glBuffer);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");

		m_ctx.bufferData(targetToGL(target), size, ptr, usageToGL(usage));
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBufferData()");
	}
	else if (m_storage == STORAGE_USER)
	{
		if (m_data)
			delete[] m_data;

		m_data = new char[size];
		std::memcpy(m_data, ptr, size);
	}
	else
		DE_ASSERT(false);
}

void ContextArray::subdata (Target target, int offset, int size, const char* ptr)
{
	m_target = target;

	if (m_storage == STORAGE_BUFFER)
	{
		m_ctx.bindBuffer(targetToGL(target), m_glBuffer);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");

		m_ctx.bufferSubData(targetToGL(target), offset, size, ptr);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBufferSubData()");
	}
	else if (m_storage == STORAGE_USER)
		std::memcpy(m_data + offset, ptr, size);
	else
		DE_ASSERT(false);
}

void ContextArray::bind (int attribNdx, int offset, int size, InputType inputType, OutputType outType, bool normalized, int stride)
{
	m_attribNdx			= attribNdx;
	m_bound				= true;
	m_componentCount	= size;
	m_inputType			= inputType;
	m_outputType		= outType;
	m_normalize			= normalized;
	m_stride			= stride;
	m_offset			= offset;
}

void ContextArray::bindIndexArray (Array::Target target)
{
	if (m_storage == STORAGE_USER)
	{
	}
	else if (m_storage == STORAGE_BUFFER)
	{
		m_ctx.bindBuffer(targetToGL(target), m_glBuffer);
	}
}

void ContextArray::glBind (deUint32 loc)
{
	if (m_storage == STORAGE_BUFFER)
	{
		m_ctx.bindBuffer(targetToGL(m_target), m_glBuffer);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");

		if (!inputTypeIsFloatType(m_inputType))
		{
			// Input is not float type

			if (outputTypeIsFloatType(m_outputType))
			{
				// Output type is float type
				m_ctx.vertexAttribPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_normalize, m_stride, (GLvoid*)((GLintptr)m_offset));
				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
			}
			else
			{
				// Output type is int type
				m_ctx.vertexAttribIPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_stride, (GLvoid*)((GLintptr)m_offset));
				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribIPointer()");
			}
		}
		else
		{
			// Input type is float type

			// Output type must be float type
			DE_ASSERT(m_outputType == OUTPUTTYPE_FLOAT || m_outputType == OUTPUTTYPE_VEC2 || m_outputType == OUTPUTTYPE_VEC3 || m_outputType == OUTPUTTYPE_VEC4);

			m_ctx.vertexAttribPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_normalize, m_stride, (GLvoid*)((GLintptr)m_offset));
			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
		}

		m_ctx.bindBuffer(targetToGL(m_target), 0);
	}
	else if (m_storage == STORAGE_USER)
	{
		m_ctx.bindBuffer(targetToGL(m_target), 0);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");

		if (!inputTypeIsFloatType(m_inputType))
		{
			// Input is not float type

			if (outputTypeIsFloatType(m_outputType))
			{
				// Output type is float type
				m_ctx.vertexAttribPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_normalize, m_stride, m_data + m_offset);
				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
			}
			else
			{
				// Output type is int type
				m_ctx.vertexAttribIPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_stride, m_data + m_offset);
				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribIPointer()");
			}
		}
		else
		{
			// Input type is float type

			// Output type must be float type
			DE_ASSERT(m_outputType == OUTPUTTYPE_FLOAT || m_outputType == OUTPUTTYPE_VEC2 || m_outputType == OUTPUTTYPE_VEC3 || m_outputType == OUTPUTTYPE_VEC4);

			m_ctx.vertexAttribPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_normalize, m_stride, m_data + m_offset);
			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
		}
	}
	else
		DE_ASSERT(false);
}

GLenum ContextArray::targetToGL (Array::Target target)
{
	DE_ASSERT(target < TARGET_LAST);

	static const GLenum targets[] =
	{
		GL_ELEMENT_ARRAY_BUFFER,	// TARGET_ELEMENT_ARRAY = 0,
		GL_ARRAY_BUFFER				// TARGET_ARRAY,
	};

	return targets[(int)target];
}

GLenum ContextArray::usageToGL (Array::Usage usage)
{
	DE_ASSERT(usage < USAGE_LAST);

	static const GLenum usages[] =
	{
		GL_DYNAMIC_DRAW,	// USAGE_DYNAMIC_DRAW = 0,
		GL_STATIC_DRAW,		// USAGE_STATIC_DRAW,
		GL_STREAM_DRAW,		// USAGE_STREAM_DRAW,

		GL_STREAM_READ,		// USAGE_STREAM_READ,
		GL_STREAM_COPY,		// USAGE_STREAM_COPY,

		GL_STATIC_READ,		// USAGE_STATIC_READ,
		GL_STATIC_COPY,		// USAGE_STATIC_COPY,

		GL_DYNAMIC_READ,	// USAGE_DYNAMIC_READ,
		GL_DYNAMIC_COPY		// USAGE_DYNAMIC_COPY,
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(usages) == Array::USAGE_LAST);

	return usages[(int)usage];
}

GLenum ContextArray::inputTypeToGL (Array::InputType type)
{
	DE_ASSERT(type < INPUTTYPE_LAST);

	static const GLenum types[] =
	{
		GL_FLOAT,				// INPUTTYPE_FLOAT = 0,
		GL_FIXED,				// INPUTTYPE_FIXED,
		GL_DOUBLE,				// INPUTTYPE_DOUBLE
		GL_BYTE,				// INPUTTYPE_BYTE,
		GL_SHORT,				// INPUTTYPE_SHORT,
		GL_UNSIGNED_BYTE,		// INPUTTYPE_UNSIGNED_BYTE,
		GL_UNSIGNED_SHORT,		// INPUTTYPE_UNSIGNED_SHORT,

		GL_INT,					// INPUTTYPE_INT,
		GL_UNSIGNED_INT,		// INPUTTYPE_UNSIGNED_INT,
		GL_HALF_FLOAT,			// INPUTTYPE_HALF,
		GL_UNSIGNED_INT_2_10_10_10_REV, // INPUTTYPE_UNSIGNED_INT_2_10_10_10,
		GL_INT_2_10_10_10_REV			// INPUTTYPE_INT_2_10_10_10,
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == Array::INPUTTYPE_LAST);

	return types[(int)type];
}

std::string ContextArray::outputTypeToGLType (Array::OutputType type)
{
	DE_ASSERT(type < OUTPUTTYPE_LAST);

	static const char* types[] =
	{
		"float",		// OUTPUTTYPE_FLOAT = 0,
		"vec2",			// OUTPUTTYPE_VEC2,
		"vec3",			// OUTPUTTYPE_VEC3,
		"vec4",			// OUTPUTTYPE_VEC4,

		"int",			// OUTPUTTYPE_INT,
		"uint",			// OUTPUTTYPE_UINT,

		"ivec2",		// OUTPUTTYPE_IVEC2,
		"ivec3",		// OUTPUTTYPE_IVEC3,
		"ivec4",		// OUTPUTTYPE_IVEC4,

		"uvec2",		// OUTPUTTYPE_UVEC2,
		"uvec3",		// OUTPUTTYPE_UVEC3,
		"uvec4",		// OUTPUTTYPE_UVEC4,
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(types) == Array::OUTPUTTYPE_LAST);

	return types[type];
}

GLenum ContextArray::primitiveToGL (Array::Primitive primitive)
{
	GLenum primitives[] =
	{
		GL_POINTS,			// PRIMITIVE_POINTS = 0,
		GL_TRIANGLES,		// PRIMITIVE_TRIANGLES,
		GL_TRIANGLE_FAN,	// PRIMITIVE_TRIANGLE_FAN,
		GL_TRIANGLE_STRIP	// PRIMITIVE_TRIANGLE_STRIP,
	};
	DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(primitives) == Array::PRIMITIVE_LAST);

	return primitives[(int)primitive];
}

ContextArrayPack::ContextArrayPack (glu::RenderContext& renderCtx, sglr::Context& drawContext)
	: m_renderCtx	(renderCtx)
	, m_ctx			(drawContext)
	, m_program		(DE_NULL)
	, m_screen		(std::min(512, renderCtx.getRenderTarget().getWidth()), std::min(512, renderCtx.getRenderTarget().getHeight()))
{
}

ContextArrayPack::~ContextArrayPack (void)
{
	for (std::vector<ContextArray*>::iterator itr = m_arrays.begin(); itr != m_arrays.end(); itr++)
		delete *itr;

	delete m_program;
}

int ContextArrayPack::getArrayCount (void)
{
	return (int)m_arrays.size();
}

void ContextArrayPack::newArray (Array::Storage storage)
{
	m_arrays.push_back(new ContextArray(storage, m_ctx));
}

class ContextShaderProgram : public sglr::ShaderProgram
{
public:
												ContextShaderProgram		(const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays);

	void										shadeVertices				(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
	void										shadeFragments				(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;

private:
	static std::string							genVertexSource				(const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays);
	static std::string							genFragmentSource			(const glu::RenderContext& ctx);
	static rr::GenericVecType					mapOutputType				(const Array::OutputType& type);
	static int									getComponentCount			(const Array::OutputType& type);

	static sglr::pdec::ShaderProgramDeclaration createProgramDeclaration	(const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays);

	std::vector<int>							m_componentCount;
	std::vector<rr::GenericVecType>				m_attrType;
};

ContextShaderProgram::ContextShaderProgram (const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays)
	: sglr::ShaderProgram	(createProgramDeclaration(ctx, arrays))
	, m_componentCount		(arrays.size())
	, m_attrType			(arrays.size())
{
	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
	{
		m_componentCount[arrayNdx]	= getComponentCount(arrays[arrayNdx]->getOutputType());
		m_attrType[arrayNdx]		= mapOutputType(arrays[arrayNdx]->getOutputType());
	}
}

template <typename T>
void calcShaderColorCoord (tcu::Vec2& coord, tcu::Vec3& color, const tcu::Vector<T, 4>& attribValue, bool isCoordinate, int numComponents)
{
	if (isCoordinate)
		switch (numComponents)
		{
			case 1:	coord = tcu::Vec2((float)attribValue.x(),					(float)attribValue.x());					break;
			case 2:	coord = tcu::Vec2((float)attribValue.x(),					(float)attribValue.y());					break;
			case 3:	coord = tcu::Vec2((float)attribValue.x() + attribValue.z(),	(float)attribValue.y());					break;
			case 4:	coord = tcu::Vec2((float)attribValue.x() + attribValue.z(),	(float)attribValue.y() + attribValue.w());	break;

			default:
				DE_ASSERT(false);
		}
	else
	{
		switch (numComponents)
		{
			case 1:
				color = color * (float)attribValue.x();
				break;

			case 2:
				color.x() = color.x() * attribValue.x();
				color.y() = color.y() * attribValue.y();
				break;

			case 3:
				color.x() = color.x() * attribValue.x();
				color.y() = color.y() * attribValue.y();
				color.z() = color.z() * attribValue.z();
				break;

			case 4:
				color.x() = color.x() * attribValue.x() * attribValue.w();
				color.y() = color.y() * attribValue.y() * attribValue.w();
				color.z() = color.z() * attribValue.z() * attribValue.w();
				break;

			default:
				DE_ASSERT(false);
		}
	}
}

void ContextShaderProgram::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
{
	const float	u_coordScale = getUniformByName("u_coordScale").value.f;
	const float u_colorScale = getUniformByName("u_colorScale").value.f;

	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		const size_t varyingLocColor = 0;

		rr::VertexPacket& packet = *packets[packetNdx];

		// Calc output color
		tcu::Vec2 coord = tcu::Vec2(1.0, 1.0);
		tcu::Vec3 color = tcu::Vec3(1.0, 1.0, 1.0);

		for (int attribNdx = 0; attribNdx < (int)m_attrType.size(); attribNdx++)
		{
			const int numComponents = m_componentCount[attribNdx];

			switch (m_attrType[attribNdx])
			{
				case rr::GENERICVECTYPE_FLOAT:	calcShaderColorCoord(coord, color, rr::readVertexAttribFloat(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), attribNdx == 0, numComponents);	break;
				case rr::GENERICVECTYPE_INT32:	calcShaderColorCoord(coord, color, rr::readVertexAttribInt	(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), attribNdx == 0, numComponents);	break;
				case rr::GENERICVECTYPE_UINT32:	calcShaderColorCoord(coord, color, rr::readVertexAttribUint	(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), attribNdx == 0, numComponents);	break;
				default:
					DE_ASSERT(false);
			}
		}

		// Transform position
		{
			packet.position = tcu::Vec4(u_coordScale * coord.x(), u_coordScale * coord.y(), 1.0f, 1.0f);
		}

		// Pass color to FS
		{
			packet.outputs[varyingLocColor] = tcu::Vec4(u_colorScale * color.x(), u_colorScale * color.y(), u_colorScale * color.z(), 1.0f);
		}
	}
}

void ContextShaderProgram::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
{
	const size_t varyingLocColor = 0;

	// Triangles are flashaded
	tcu::Vec4 color = rr::readTriangleVarying<float>(packets[0], context, varyingLocColor, 0);

	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, color);
}

std::string ContextShaderProgram::genVertexSource (const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays)
{
	std::stringstream vertexShaderTmpl;
	std::map<std::string, std::string> params;

	if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_300_ES))
	{
		params["VTX_IN"]		= "in";
		params["VTX_OUT"]		= "out";
		params["FRAG_IN"]		= "in";
		params["FRAG_COLOR"]	= "dEQP_FragColor";
		params["VTX_HDR"]		= "#version 300 es\n";
		params["FRAG_HDR"]		= "#version 300 es\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
	}
	else if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_100_ES))
	{
		params["VTX_IN"]		= "attribute";
		params["VTX_OUT"]		= "varying";
		params["FRAG_IN"]		= "varying";
		params["FRAG_COLOR"]	= "gl_FragColor";
		params["VTX_HDR"]		= "";
		params["FRAG_HDR"]		= "";
	}
	else if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_330))
	{
		params["VTX_IN"]		= "in";
		params["VTX_OUT"]		= "out";
		params["FRAG_IN"]		= "in";
		params["FRAG_COLOR"]	= "dEQP_FragColor";
		params["VTX_HDR"]		= "#version 330\n";
		params["FRAG_HDR"]		= "#version 330\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
	}
	else
		DE_ASSERT(DE_FALSE);

	vertexShaderTmpl << "${VTX_HDR}";

	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
	{
		vertexShaderTmpl
			<< "${VTX_IN} highp " <<  ContextArray::outputTypeToGLType(arrays[arrayNdx]->getOutputType()) << " a_" << arrays[arrayNdx]->getAttribNdx() << ";\n";
	}

	vertexShaderTmpl <<
		"uniform highp float u_coordScale;\n"
		"uniform highp float u_colorScale;\n"
		"${VTX_OUT} mediump vec4 v_color;\n"
		"void main(void)\n"
		"{\n"
		"\tgl_PointSize = 1.0;\n"
		"\thighp vec2 coord = vec2(1.0, 1.0);\n"
		"\thighp vec3 color = vec3(1.0, 1.0, 1.0);\n";

	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
	{
		if (arrays[arrayNdx]->getAttribNdx() == 0)
		{
			switch (arrays[arrayNdx]->getOutputType())
			{
				case (Array::OUTPUTTYPE_FLOAT):
					vertexShaderTmpl <<
						"\tcoord = vec2(a_0);\n";
					break;

				case (Array::OUTPUTTYPE_VEC2):
					vertexShaderTmpl <<
						"\tcoord = a_0.xy;\n";
					break;

				case (Array::OUTPUTTYPE_VEC3):
					vertexShaderTmpl <<
						"\tcoord = a_0.xy;\n"
						"\tcoord.x = coord.x + a_0.z;\n";
					break;

				case (Array::OUTPUTTYPE_VEC4):
					vertexShaderTmpl <<
						"\tcoord = a_0.xy;\n"
						"\tcoord += a_0.zw;\n";
					break;

				case (Array::OUTPUTTYPE_IVEC2):
				case (Array::OUTPUTTYPE_UVEC2):
					vertexShaderTmpl <<
						"\tcoord = vec2(a_0.xy);\n";
					break;

				case (Array::OUTPUTTYPE_IVEC3):
				case (Array::OUTPUTTYPE_UVEC3):
					vertexShaderTmpl <<
						"\tcoord = vec2(a_0.xy);\n"
						"\tcoord.x = coord.x + float(a_0.z);\n";
					break;

				case (Array::OUTPUTTYPE_IVEC4):
				case (Array::OUTPUTTYPE_UVEC4):
					vertexShaderTmpl <<
						"\tcoord = vec2(a_0.xy);\n"
						"\tcoord += vec2(a_0.zw);\n";
					break;

				default:
					DE_ASSERT(false);
					break;
			}
			continue;
		}

		switch (arrays[arrayNdx]->getOutputType())
		{
			case (Array::OUTPUTTYPE_FLOAT):
				vertexShaderTmpl <<
					"\tcolor = color * a_" << arrays[arrayNdx]->getAttribNdx() << ";\n";
				break;

			case (Array::OUTPUTTYPE_VEC2):
				vertexShaderTmpl <<
					"\tcolor.rg = color.rg * a_" << arrays[arrayNdx]->getAttribNdx() << ".xy;\n";
				break;

			case (Array::OUTPUTTYPE_VEC3):
				vertexShaderTmpl <<
					"\tcolor = color.rgb * a_" << arrays[arrayNdx]->getAttribNdx() << ".xyz;\n";
				break;

			case (Array::OUTPUTTYPE_VEC4):
				vertexShaderTmpl <<
					"\tcolor = color.rgb * a_" << arrays[arrayNdx]->getAttribNdx() << ".xyz * a_" << arrays[arrayNdx]->getAttribNdx() << ".w;\n";
				break;

			default:
				DE_ASSERT(false);
				break;
		}
	}

	vertexShaderTmpl <<
		"\tv_color = vec4(u_colorScale * color, 1.0);\n"
		"\tgl_Position = vec4(u_coordScale * coord, 1.0, 1.0);\n"
		"}\n";

	return tcu::StringTemplate(vertexShaderTmpl.str().c_str()).specialize(params);
}

std::string ContextShaderProgram::genFragmentSource (const glu::RenderContext& ctx)
{
	std::map<std::string, std::string> params;

	if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_300_ES))
	{
		params["VTX_IN"]		= "in";
		params["VTX_OUT"]		= "out";
		params["FRAG_IN"]		= "in";
		params["FRAG_COLOR"]	= "dEQP_FragColor";
		params["VTX_HDR"]		= "#version 300 es\n";
		params["FRAG_HDR"]		= "#version 300 es\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
	}
	else if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_100_ES))
	{
		params["VTX_IN"]		= "attribute";
		params["VTX_OUT"]		= "varying";
		params["FRAG_IN"]		= "varying";
		params["FRAG_COLOR"]	= "gl_FragColor";
		params["VTX_HDR"]		= "";
		params["FRAG_HDR"]		= "";
	}
	else if (glu::isGLSLVersionSupported(ctx.getType(), glu::GLSL_VERSION_330))
	{
		params["VTX_IN"]		= "in";
		params["VTX_OUT"]		= "out";
		params["FRAG_IN"]		= "in";
		params["FRAG_COLOR"]	= "dEQP_FragColor";
		params["VTX_HDR"]		= "#version 330\n";
		params["FRAG_HDR"]		= "#version 330\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
	}
	else
		DE_ASSERT(DE_FALSE);

	static const char* fragmentShaderTmpl =
		"${FRAG_HDR}"
		"${FRAG_IN} mediump vec4 v_color;\n"
		"void main(void)\n"
		"{\n"
		"\t${FRAG_COLOR} = v_color;\n"
		"}\n";

	return tcu::StringTemplate(fragmentShaderTmpl).specialize(params);
}

rr::GenericVecType ContextShaderProgram::mapOutputType (const Array::OutputType& type)
{
	switch (type)
	{
		case (Array::OUTPUTTYPE_FLOAT):
		case (Array::OUTPUTTYPE_VEC2):
		case (Array::OUTPUTTYPE_VEC3):
		case (Array::OUTPUTTYPE_VEC4):
			return rr::GENERICVECTYPE_FLOAT;

		case (Array::OUTPUTTYPE_INT):
		case (Array::OUTPUTTYPE_IVEC2):
		case (Array::OUTPUTTYPE_IVEC3):
		case (Array::OUTPUTTYPE_IVEC4):
			return rr::GENERICVECTYPE_INT32;

		case (Array::OUTPUTTYPE_UINT):
		case (Array::OUTPUTTYPE_UVEC2):
		case (Array::OUTPUTTYPE_UVEC3):
		case (Array::OUTPUTTYPE_UVEC4):
			return rr::GENERICVECTYPE_UINT32;

		default:
			DE_ASSERT(false);
			return rr::GENERICVECTYPE_LAST;
	}
}

int ContextShaderProgram::getComponentCount (const Array::OutputType& type)
{
	switch (type)
	{
		case (Array::OUTPUTTYPE_FLOAT):
		case (Array::OUTPUTTYPE_INT):
		case (Array::OUTPUTTYPE_UINT):
			return 1;

		case (Array::OUTPUTTYPE_VEC2):
		case (Array::OUTPUTTYPE_IVEC2):
		case (Array::OUTPUTTYPE_UVEC2):
			return 2;

		case (Array::OUTPUTTYPE_VEC3):
		case (Array::OUTPUTTYPE_IVEC3):
		case (Array::OUTPUTTYPE_UVEC3):
			return 3;

		case (Array::OUTPUTTYPE_VEC4):
		case (Array::OUTPUTTYPE_IVEC4):
		case (Array::OUTPUTTYPE_UVEC4):
			return 4;

		default:
			DE_ASSERT(false);
			return 0;
	}
}

sglr::pdec::ShaderProgramDeclaration ContextShaderProgram::createProgramDeclaration (const glu::RenderContext& ctx, const std::vector<ContextArray*>& arrays)
{
	sglr::pdec::ShaderProgramDeclaration decl;

	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
		decl << sglr::pdec::VertexAttribute(std::string("a_") + de::toString(arrayNdx), mapOutputType(arrays[arrayNdx]->getOutputType()));

	decl << sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT);
	decl << sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT);

	decl << sglr::pdec::VertexSource(genVertexSource(ctx, arrays));
	decl << sglr::pdec::FragmentSource(genFragmentSource(ctx));

	decl << sglr::pdec::Uniform("u_coordScale", glu::TYPE_FLOAT);
	decl << sglr::pdec::Uniform("u_colorScale", glu::TYPE_FLOAT);

	return decl;
}

void ContextArrayPack::updateProgram (void)
{
	delete m_program;
	m_program = new ContextShaderProgram(m_renderCtx, m_arrays);
}

void ContextArrayPack::render (Array::Primitive primitive, int firstVertex, int vertexCount, bool useVao, float coordScale, float colorScale)
{
	deUint32 program = 0;
	deUint32 vaoId = 0;

	updateProgram();

	m_ctx.viewport(0, 0, m_screen.getWidth(), m_screen.getHeight());
	m_ctx.clearColor(0.0, 0.0, 0.0, 1.0);
	m_ctx.clear(GL_COLOR_BUFFER_BIT);

	program = m_ctx.createProgram(m_program);

	m_ctx.useProgram(program);
	GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glUseProgram()");

	m_ctx.uniform1f(m_ctx.getUniformLocation(program, "u_coordScale"), coordScale);
	m_ctx.uniform1f(m_ctx.getUniformLocation(program, "u_colorScale"), colorScale);

	if (useVao)
	{
		m_ctx.genVertexArrays(1, &vaoId);
		m_ctx.bindVertexArray(vaoId);
	}

	for (int arrayNdx = 0; arrayNdx < (int)m_arrays.size(); arrayNdx++)
	{
		if (m_arrays[arrayNdx]->isBound())
		{
			std::stringstream attribName;
			attribName << "a_" << m_arrays[arrayNdx]->getAttribNdx();

			deUint32 loc = m_ctx.getAttribLocation(program, attribName.str().c_str());
			m_ctx.enableVertexAttribArray(loc);
			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glEnableVertexAttribArray()");

			m_arrays[arrayNdx]->glBind(loc);
		}
	}

	DE_ASSERT((firstVertex % 6) == 0);
	m_ctx.drawArrays(ContextArray::primitiveToGL(primitive), firstVertex, vertexCount - firstVertex);
	GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArrays()");

	for (int arrayNdx = 0; arrayNdx < (int)m_arrays.size(); arrayNdx++)
	{
		if (m_arrays[arrayNdx]->isBound())
		{
			std::stringstream attribName;
			attribName << "a_" << m_arrays[arrayNdx]->getAttribNdx();

			deUint32 loc = m_ctx.getAttribLocation(program, attribName.str().c_str());

			m_ctx.disableVertexAttribArray(loc);
			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDisableVertexAttribArray()");
		}
	}

	if (useVao)
		m_ctx.deleteVertexArrays(1, &vaoId);

	m_ctx.deleteProgram(program);
	m_ctx.useProgram(0);
	m_ctx.readPixels(m_screen, 0, 0, m_screen.getWidth(), m_screen.getHeight());
}

// GLValue

GLValue GLValue::getMaxValue (Array::InputType type)
{
	GLValue rangesHi[(int)Array::INPUTTYPE_LAST];

	rangesHi[(int)Array::INPUTTYPE_FLOAT]			= GLValue(Float::create(127.0f));
	rangesHi[(int)Array::INPUTTYPE_DOUBLE]			= GLValue(Double::create(127.0f));
	rangesHi[(int)Array::INPUTTYPE_BYTE]			= GLValue(Byte::create(127));
	rangesHi[(int)Array::INPUTTYPE_UNSIGNED_BYTE]	= GLValue(Ubyte::create(255));
	rangesHi[(int)Array::INPUTTYPE_UNSIGNED_SHORT]	= GLValue(Ushort::create(65530));
	rangesHi[(int)Array::INPUTTYPE_SHORT]			= GLValue(Short::create(32760));
	rangesHi[(int)Array::INPUTTYPE_FIXED]			= GLValue(Fixed::create(32760));
	rangesHi[(int)Array::INPUTTYPE_INT]				= GLValue(Int::create(2147483647));
	rangesHi[(int)Array::INPUTTYPE_UNSIGNED_INT]	= GLValue(Uint::create(4294967295u));
	rangesHi[(int)Array::INPUTTYPE_HALF]			= GLValue(Half::create(256.0f));

	return rangesHi[(int)type];
}

GLValue GLValue::getMinValue (Array::InputType type)
{
	GLValue rangesLo[(int)Array::INPUTTYPE_LAST];

	rangesLo[(int)Array::INPUTTYPE_FLOAT]			= GLValue(Float::create(-127.0f));
	rangesLo[(int)Array::INPUTTYPE_DOUBLE]			= GLValue(Double::create(-127.0f));
	rangesLo[(int)Array::INPUTTYPE_BYTE]			= GLValue(Byte::create(-127));
	rangesLo[(int)Array::INPUTTYPE_UNSIGNED_BYTE]	= GLValue(Ubyte::create(0));
	rangesLo[(int)Array::INPUTTYPE_UNSIGNED_SHORT]	= GLValue(Ushort::create(0));
	rangesLo[(int)Array::INPUTTYPE_SHORT]			= GLValue(Short::create(-32760));
	rangesLo[(int)Array::INPUTTYPE_FIXED]			= GLValue(Fixed::create(-32760));
	rangesLo[(int)Array::INPUTTYPE_INT]				= GLValue(Int::create(-2147483647));
	rangesLo[(int)Array::INPUTTYPE_UNSIGNED_INT]	= GLValue(Uint::create(0));
	rangesLo[(int)Array::INPUTTYPE_HALF]			= GLValue(Half::create(-256.0f));

	return rangesLo[(int)type];
}

float GLValue::toFloat (void) const
{
	switch (type)
	{
		case Array::INPUTTYPE_FLOAT:
			return fl.getValue();
			break;

		case Array::INPUTTYPE_BYTE:
			return b.getValue();
			break;

		case Array::INPUTTYPE_UNSIGNED_BYTE:
			return ub.getValue();
			break;

		case Array::INPUTTYPE_SHORT:
			return s.getValue();
			break;

		case Array::INPUTTYPE_UNSIGNED_SHORT:
			return us.getValue();
			break;

		case Array::INPUTTYPE_FIXED:
		{
			int maxValue = 65536;
			return (float)(double(2 * fi.getValue() + 1) / (maxValue - 1));

			break;
		}

		case Array::INPUTTYPE_UNSIGNED_INT:
			return (float)ui.getValue();
			break;

		case Array::INPUTTYPE_INT:
			return (float)i.getValue();
			break;

		case Array::INPUTTYPE_HALF:
			return h.to<float>();
			break;

		case Array::INPUTTYPE_DOUBLE:
			return (float)d.getValue();
			break;

		default:
			DE_ASSERT(false);
			return 0.0f;
			break;
	};
}

class RandomArrayGenerator
{
public:
	static char*	generateArray			(int seed, GLValue min, GLValue max, int count, int componentCount, int stride, Array::InputType type);
	static char*	generateQuads			(int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive, Array::InputType type, GLValue min, GLValue max);
	static char*	generatePerQuad			(int seed, int count, int componentCount, int stride, Array::Primitive primitive, Array::InputType type, GLValue min, GLValue max);

private:
	template<typename T>
	static char*	createQuads		(int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive, T min, T max);
	template<typename T>
	static char*	createPerQuads	(int seed, int count, int componentCount, int stride, Array::Primitive primitive, T min, T max);
	static char*	createQuadsPacked (int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive);
	static void		setData			(char* data, Array::InputType type, deRandom& rnd, GLValue min, GLValue max);
};

void RandomArrayGenerator::setData (char* data, Array::InputType type, deRandom& rnd, GLValue min, GLValue max)
{
	switch (type)
	{
		case Array::INPUTTYPE_FLOAT:
		{
			alignmentSafeAssignment<float>(data, getRandom<GLValue::Float>(rnd, min.fl, max.fl));
			break;
		}

		case Array::INPUTTYPE_DOUBLE:
		{
			alignmentSafeAssignment<double>(data, getRandom<GLValue::Float>(rnd, min.fl, max.fl));
			break;
		}

		case Array::INPUTTYPE_SHORT:
		{
			alignmentSafeAssignment<deInt16>(data, getRandom<GLValue::Short>(rnd, min.s, max.s));
			break;
		}

		case Array::INPUTTYPE_UNSIGNED_SHORT:
		{
			alignmentSafeAssignment<deUint16>(data, getRandom<GLValue::Ushort>(rnd, min.us, max.us));
			break;
		}

		case Array::INPUTTYPE_BYTE:
		{
			alignmentSafeAssignment<deInt8>(data, getRandom<GLValue::Byte>(rnd, min.b, max.b));
			break;
		}

		case Array::INPUTTYPE_UNSIGNED_BYTE:
		{
			alignmentSafeAssignment<deUint8>(data, getRandom<GLValue::Ubyte>(rnd, min.ub, max.ub));
			break;
		}

		case Array::INPUTTYPE_FIXED:
		{
			alignmentSafeAssignment<deInt32>(data, getRandom<GLValue::Fixed>(rnd, min.fi, max.fi));
			break;
		}

		case Array::INPUTTYPE_INT:
		{
			alignmentSafeAssignment<deInt32>(data, getRandom<GLValue::Int>(rnd, min.i, max.i));
			break;
		}

		case Array::INPUTTYPE_UNSIGNED_INT:
		{
			alignmentSafeAssignment<deUint32>(data, getRandom<GLValue::Uint>(rnd, min.ui, max.ui));
			break;
		}

		case Array::INPUTTYPE_HALF:
		{
			alignmentSafeAssignment<deFloat16>(data, getRandom<GLValue::Half>(rnd, min.h, max.h).getValue());
			break;
		}

		default:
			DE_ASSERT(false);
			break;
	}
}

char* RandomArrayGenerator::generateArray (int seed, GLValue min, GLValue max, int count, int componentCount, int stride, Array::InputType type)
{
	char* data = NULL;

	deRandom rnd;
	deRandom_init(&rnd, seed);

	if (stride == 0)
		stride = componentCount * Array::inputTypeSize(type);

	data = new char[stride * count];

	for (int vertexNdx = 0; vertexNdx < count; vertexNdx++)
	{
		for (int componentNdx = 0; componentNdx < componentCount; componentNdx++)
		{
			setData(&(data[vertexNdx * stride + Array::inputTypeSize(type) * componentNdx]), type, rnd, min, max);
		}
	}

	return data;
}

char* RandomArrayGenerator::generateQuads (int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive, Array::InputType type, GLValue min, GLValue max)
{
	char* data = DE_NULL;

	switch (type)
	{
		case Array::INPUTTYPE_FLOAT:
			data = createQuads<GLValue::Float>(seed, count, componentCount, offset, stride, primitive, min.fl, max.fl);
			break;

		case Array::INPUTTYPE_FIXED:
			data = createQuads<GLValue::Fixed>(seed, count, componentCount, offset, stride, primitive, min.fi, max.fi);
			break;

		case Array::INPUTTYPE_DOUBLE:
			data = createQuads<GLValue::Double>(seed, count, componentCount, offset, stride, primitive, min.d, max.d);
			break;

		case Array::INPUTTYPE_BYTE:
			data = createQuads<GLValue::Byte>(seed, count, componentCount, offset, stride, primitive, min.b, max.b);
			break;

		case Array::INPUTTYPE_SHORT:
			data = createQuads<GLValue::Short>(seed, count, componentCount, offset, stride, primitive, min.s, max.s);
			break;

		case Array::INPUTTYPE_UNSIGNED_BYTE:
			data = createQuads<GLValue::Ubyte>(seed, count, componentCount, offset, stride, primitive, min.ub, max.ub);
			break;

		case Array::INPUTTYPE_UNSIGNED_SHORT:
			data = createQuads<GLValue::Ushort>(seed, count, componentCount, offset, stride, primitive, min.us, max.us);
			break;

		case Array::INPUTTYPE_UNSIGNED_INT:
			data = createQuads<GLValue::Uint>(seed, count, componentCount, offset, stride, primitive, min.ui, max.ui);
			break;

		case Array::INPUTTYPE_INT:
			data = createQuads<GLValue::Int>(seed, count, componentCount, offset, stride, primitive, min.i, max.i);
			break;

		case Array::INPUTTYPE_HALF:
			data = createQuads<GLValue::Half>(seed, count, componentCount, offset, stride, primitive, min.h, max.h);
			break;

		case Array::INPUTTYPE_INT_2_10_10_10:
		case Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10:
			data = createQuadsPacked(seed, count, componentCount, offset, stride, primitive);
			break;

		default:
			DE_ASSERT(false);
			break;
	}

	return data;
}

char* RandomArrayGenerator::createQuadsPacked (int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive)
{
	DE_ASSERT(componentCount == 4);
	DE_UNREF(componentCount);
	int quadStride = 0;

	if (stride == 0)
		stride = sizeof(deUint32);

	switch (primitive)
	{
		case Array::PRIMITIVE_TRIANGLES:
			quadStride = stride * 6;
			break;

		default:
			DE_ASSERT(false);
			break;
	}

	char* const _data		= new char[offset + quadStride * (count - 1) + stride * 5 + componentCount * Array::inputTypeSize(Array::INPUTTYPE_INT_2_10_10_10)]; // last element must be fully in the array
	char* const resultData	= _data + offset;

	const deUint32 max		= 1024;
	const deUint32 min		= 10;
	const deUint32 max2		= 4;

	deRandom rnd;
	deRandom_init(&rnd,  seed);

	switch (primitive)
	{
		case Array::PRIMITIVE_TRIANGLES:
		{
			for (int quadNdx = 0; quadNdx < count; quadNdx++)
			{
				deUint32 x1	= min + deRandom_getUint32(&rnd) % (max - min);
				deUint32 x2	= min + deRandom_getUint32(&rnd) % (max - x1);

				deUint32 y1	= min + deRandom_getUint32(&rnd) % (max - min);
				deUint32 y2	= min + deRandom_getUint32(&rnd) % (max - y1);

				deUint32 z	= min + deRandom_getUint32(&rnd) % (max - min);
				deUint32 w	= deRandom_getUint32(&rnd) % max2;

				deUint32 val1 = (w << 30) | (z << 20) | (y1 << 10) | x1;
				deUint32 val2 = (w << 30) | (z << 20) | (y1 << 10) | x2;
				deUint32 val3 = (w << 30) | (z << 20) | (y2 << 10) | x1;

				deUint32 val4 = (w << 30) | (z << 20) | (y2 << 10) | x1;
				deUint32 val5 = (w << 30) | (z << 20) | (y1 << 10) | x2;
				deUint32 val6 = (w << 30) | (z << 20) | (y2 << 10) | x2;

				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 0]), val1);
				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 1]), val2);
				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 2]), val3);
				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 3]), val4);
				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 4]), val5);
				alignmentSafeAssignment<deUint32>(&(resultData[quadNdx * quadStride + stride * 5]), val6);
			}

			break;
		}

		default:
			DE_ASSERT(false);
			break;
	}

	return _data;
}

template<typename T>
char* RandomArrayGenerator::createQuads (int seed, int count, int componentCount, int offset, int stride, Array::Primitive primitive, T min, T max)
{
	int componentStride = sizeof(T);
	int quadStride = 0;

	if (stride == 0)
		stride = componentCount * componentStride;
	DE_ASSERT(stride >= componentCount * componentStride);

	switch (primitive)
	{
		case Array::PRIMITIVE_TRIANGLES:
			quadStride = stride * 6;
			break;

		default:
			DE_ASSERT(false);
			break;
	}

	char* resultData = new char[offset + quadStride * count];
	char* _data = resultData;
	resultData = resultData + offset;

	deRandom rnd;
	deRandom_init(&rnd,  seed);

	switch (primitive)
	{
		case Array::PRIMITIVE_TRIANGLES:
		{
			for (int quadNdx = 0; quadNdx < count; ++quadNdx)
			{
				T x1, x2;
				T y1, y2;
				T z, w;

				// attempt to find a good (i.e not extremely small) quad
				for (int attemptNdx = 0; attemptNdx < 4; ++attemptNdx)
				{
					x1 = getRandom<T>(rnd, min, max);
					x2 = getRandom<T>(rnd, minValue<T>(), abs<T>(max - x1));

					y1 = getRandom<T>(rnd, min, max);
					y2 = getRandom<T>(rnd, minValue<T>(), abs<T>(max - y1));

					z = (componentCount > 2) ? (getRandom<T>(rnd, min, max)) : (T::create(0));
					w = (componentCount > 3) ? (getRandom<T>(rnd, min, max)) : (T::create(1));

					// no additional components, all is good
					if (componentCount <= 2)
						break;

					// The result quad is too thin?
					if ((deFloatAbs(x2.template to<float>() + z.template to<float>()) < minValue<T>().template to<float>()) ||
						(deFloatAbs(y2.template to<float>() + w.template to<float>()) < minValue<T>().template to<float>()))
						continue;

					// all ok
					break;
				}

				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride]), x1);
				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + componentStride]), y1);

				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride]), x1 + x2);
				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride + componentStride]), y1);

				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 2]), x1);
				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 2 + componentStride]), y1 + y2);

				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 3]), x1);
				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 3 + componentStride]), y1 + y2);

				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 4]), x1 + x2);
				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 4 + componentStride]), y1);

				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 5]), x1 + x2);
				alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * 5 + componentStride]), y1 + y2);

				if (componentCount > 2)
				{
					for (int i = 0; i < 6; i++)
						alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * i + componentStride * 2]), z);
				}

				if (componentCount > 3)
				{
					for (int i = 0; i < 6; i++)
						alignmentSafeAssignment<T>(&(resultData[quadNdx * quadStride + stride * i + componentStride * 3]), w);
				}
			}

			break;
		}

		default:
			DE_ASSERT(false);
			break;
	}

	return _data;
}

char* RandomArrayGenerator::generatePerQuad (int seed, int count, int componentCount, int stride, Array::Primitive primitive, Array::InputType type, GLValue min, GLValue max)
{
	char* data = DE_NULL;

	switch (type)
	{
		case Array::INPUTTYPE_FLOAT:
			data = createPerQuads<GLValue::Float>(seed, count, componentCount, stride, primitive, min.fl, max.fl);
			break;

		case Array::INPUTTYPE_FIXED:
			data = createPerQuads<GLValue::Fixed>(seed, count, componentCount, stride, primitive, min.fi, max.fi);
			break;

		case Array::INPUTTYPE_DOUBLE:
			data = createPerQuads<GLValue::Double>(seed, count, componentCount, stride, primitive, min.d, max.d);
			break;

		case Array::INPUTTYPE_BYTE:
			data = createPerQuads<GLValue::Byte>(seed, count, componentCount, stride, primitive, min.b, max.b);
			break;

		case Array::INPUTTYPE_SHORT:
			data = createPerQuads<GLValue::Short>(seed, count, componentCount, stride, primitive, min.s, max.s);
			break;

		case Array::INPUTTYPE_UNSIGNED_BYTE:
			data = createPerQuads<GLValue::Ubyte>(seed, count, componentCount, stride, primitive, min.ub, max.ub);
			break;

		case Array::INPUTTYPE_UNSIGNED_SHORT:
			data = createPerQuads<GLValue::Ushort>(seed, count, componentCount, stride, primitive, min.us, max.us);
			break;

		case Array::INPUTTYPE_UNSIGNED_INT:
			data = createPerQuads<GLValue::Uint>(seed, count, componentCount, stride, primitive, min.ui, max.ui);
			break;

		case Array::INPUTTYPE_INT:
			data = createPerQuads<GLValue::Int>(seed, count, componentCount, stride, primitive, min.i, max.i);
			break;

		case Array::INPUTTYPE_HALF:
			data = createPerQuads<GLValue::Half>(seed, count, componentCount, stride, primitive, min.h, max.h);
			break;

		default:
			DE_ASSERT(false);
			break;
	}

	return data;
}

template<typename T>
char* RandomArrayGenerator::createPerQuads (int seed, int count, int componentCount, int stride, Array::Primitive primitive, T min, T max)
{
	deRandom rnd;
	deRandom_init(&rnd, seed);

	int componentStride = sizeof(T);

	if (stride == 0)
		stride = componentStride * componentCount;

	int quadStride = 0;

	switch (primitive)
	{
		case Array::PRIMITIVE_TRIANGLES:
			quadStride = stride * 6;
			break;

		default:
			DE_ASSERT(false);
			break;
	}

	char* data = new char[count * quadStride];

	for (int quadNdx = 0; quadNdx < count; quadNdx++)
	{
		for (int componentNdx = 0; componentNdx < componentCount; componentNdx++)
		{
			T val = getRandom<T>(rnd, min, max);

			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 0 + componentStride * componentNdx, val);
			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 1 + componentStride * componentNdx, val);
			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 2 + componentStride * componentNdx, val);
			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 3 + componentStride * componentNdx, val);
			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 4 + componentStride * componentNdx, val);
			alignmentSafeAssignment<T>(data + quadNdx * quadStride + stride * 5 + componentStride * componentNdx, val);
		}
	}

	return data;
}

// VertexArrayTest

VertexArrayTest::VertexArrayTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name ,const char* desc)
	: TestCase			(testCtx, name, desc)
	, m_renderCtx		(renderCtx)
	, m_refBuffers		(DE_NULL)
	, m_refContext		(DE_NULL)
	, m_glesContext		(DE_NULL)
	, m_glArrayPack		(DE_NULL)
	, m_rrArrayPack		(DE_NULL)
	, m_isOk			(false)
	, m_maxDiffRed		(deCeilFloatToInt32(256.0f * (2.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().redBits))))
	, m_maxDiffGreen	(deCeilFloatToInt32(256.0f * (2.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().greenBits))))
	, m_maxDiffBlue		(deCeilFloatToInt32(256.0f * (2.0f / (1 << m_renderCtx.getRenderTarget().getPixelFormat().blueBits))))
{
}

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

void VertexArrayTest::init (void)
{
	const int						renderTargetWidth	= de::min(512, m_renderCtx.getRenderTarget().getWidth());
	const int						renderTargetHeight	= de::min(512, m_renderCtx.getRenderTarget().getHeight());
	sglr::ReferenceContextLimits	limits				(m_renderCtx);

	m_glesContext		= new sglr::GLContext(m_renderCtx, m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS | sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(0, 0, renderTargetWidth, renderTargetHeight));

	m_refBuffers		= new sglr::ReferenceContextBuffers(m_renderCtx.getRenderTarget().getPixelFormat(), 0, 0, renderTargetWidth, renderTargetHeight);
	m_refContext		= new sglr::ReferenceContext(limits, m_refBuffers->getColorbuffer(), m_refBuffers->getDepthbuffer(), m_refBuffers->getStencilbuffer());

	m_glArrayPack		= new ContextArrayPack(m_renderCtx, *m_glesContext);
	m_rrArrayPack		= new ContextArrayPack(m_renderCtx, *m_refContext);
}

void VertexArrayTest::deinit (void)
{
	delete m_glArrayPack;
	delete m_rrArrayPack;
	delete m_refBuffers;
	delete m_refContext;
	delete m_glesContext;

	m_glArrayPack	= DE_NULL;
	m_rrArrayPack	= DE_NULL;
	m_refBuffers	= DE_NULL;
	m_refContext	= DE_NULL;
	m_glesContext	= DE_NULL;
}

void VertexArrayTest::compare (void)
{
	const tcu::Surface&	ref		= m_rrArrayPack->getSurface();
	const tcu::Surface&	screen	= m_glArrayPack->getSurface();

	if (m_renderCtx.getRenderTarget().getNumSamples() > 1)
	{
		// \todo [mika] Improve compare when using multisampling
		m_testCtx.getLog() << tcu::TestLog::Message << "Warning: Comparision of result from multisample render targets are not as stricts as without multisampling. Might produce false positives!" << tcu::TestLog::EndMessage;
		m_isOk = tcu::fuzzyCompare(m_testCtx.getLog(), "Compare Results", "Compare Results", ref.getAccess(), screen.getAccess(), 1.5f, tcu::COMPARE_LOG_RESULT);
	}
	else
	{
		tcu::RGBA		threshold	(m_maxDiffRed, m_maxDiffGreen, m_maxDiffBlue, 255);
		tcu::Surface	error		(ref.getWidth(), ref.getHeight());

		m_isOk = true;

		for (int y = 1; y < ref.getHeight()-1; y++)
		{
			for (int x = 1; x < ref.getWidth()-1; x++)
			{
				tcu::RGBA	refPixel		= ref.getPixel(x, y);
				tcu::RGBA	screenPixel		= screen.getPixel(x, y);
				bool		isOkPixel		= false;

				// Don't do comparisons for this pixel if it belongs to a one-pixel-thin part (i.e. it doesn't have similar-color neighbors in both x and y directions) in both result and reference.
				// This fixes some false negatives.
				bool		refThin			= (!tcu::compareThreshold(refPixel, ref.getPixel(x-1, y  ), threshold) && !tcu::compareThreshold(refPixel, ref.getPixel(x+1, y  ), threshold)) ||
											  (!tcu::compareThreshold(refPixel, ref.getPixel(x  , y-1), threshold) && !tcu::compareThreshold(refPixel, ref.getPixel(x  , y+1), threshold));
				bool		screenThin		= (!tcu::compareThreshold(screenPixel, screen.getPixel(x-1, y  ), threshold) && !tcu::compareThreshold(screenPixel, screen.getPixel(x+1, y  ), threshold)) ||
											  (!tcu::compareThreshold(screenPixel, screen.getPixel(x  , y-1), threshold) && !tcu::compareThreshold(screenPixel, screen.getPixel(x  , y+1), threshold));

				if (refThin && screenThin)
					isOkPixel = true;
				else
				{
					for (int dy = -1; dy < 2 && !isOkPixel; dy++)
					{
						for (int dx = -1; dx < 2 && !isOkPixel; dx++)
						{
							// Check reference pixel against screen pixel
							{
								tcu::RGBA	screenCmpPixel	= screen.getPixel(x+dx, y+dy);
								deUint8		r				= deAbs32(refPixel.getRed()		- screenCmpPixel.getRed());
								deUint8		g				= deAbs32(refPixel.getGreen()	- screenCmpPixel.getGreen());
								deUint8		b				= deAbs32(refPixel.getBlue()	- screenCmpPixel.getBlue());

								if (r <= m_maxDiffRed && g <= m_maxDiffGreen && b <= m_maxDiffBlue)
									isOkPixel = true;
							}

							// Check screen pixels against reference pixel
							{
								tcu::RGBA	refCmpPixel		= ref.getPixel(x+dx, y+dy);
								deUint8		r				= deAbs32(refCmpPixel.getRed()		- screenPixel.getRed());
								deUint8		g				= deAbs32(refCmpPixel.getGreen()	- screenPixel.getGreen());
								deUint8		b				= deAbs32(refCmpPixel.getBlue()		- screenPixel.getBlue());

								if (r <= m_maxDiffRed && g <= m_maxDiffGreen && b <= m_maxDiffBlue)
									isOkPixel = true;
							}
						}
					}
				}

				if (isOkPixel)
					error.setPixel(x, y, tcu::RGBA(screen.getPixel(x, y).getRed(), (screen.getPixel(x, y).getGreen() + 255) / 2, screen.getPixel(x, y).getBlue(), 255));
				else
				{
					error.setPixel(x, y, tcu::RGBA(255, 0, 0, 255));
					m_isOk = false;
				}
			}
		}

		tcu::TestLog& log = m_testCtx.getLog();
		if (!m_isOk)
		{
			log << TestLog::Message << "Image comparison failed, threshold = (" << m_maxDiffRed << ", " << m_maxDiffGreen << ", " << m_maxDiffBlue << ")" << TestLog::EndMessage;
			log << TestLog::ImageSet("Compare result", "Result of rendering")
				<< TestLog::Image("Result",		"Result",		screen)
				<< TestLog::Image("Reference",	"Reference",	ref)
				<< TestLog::Image("ErrorMask",	"Error mask",	error)
				<< TestLog::EndImageSet;
		}
		else
		{
			log << TestLog::ImageSet("Compare result", "Result of rendering")
				<< TestLog::Image("Result", "Result", screen)
				<< TestLog::EndImageSet;
		}
	}
}

// MultiVertexArrayTest

MultiVertexArrayTest::Spec::ArraySpec::ArraySpec(Array::InputType inputType_, Array::OutputType outputType_, Array::Storage storage_, Array::Usage usage_, int componentCount_, int offset_, int stride_, bool normalize_, GLValue min_, GLValue max_)
	: inputType		(inputType_)
	, outputType	(outputType_)
	, storage		(storage_)
	, usage			(usage_)
	, componentCount(componentCount_)
	, offset		(offset_)
	, stride		(stride_)
	, normalize		(normalize_)
	, min			(min_)
	, max			(max_)
{
}

std::string MultiVertexArrayTest::Spec::getName (void) const
{
	std::stringstream name;

	for (size_t ndx = 0; ndx < arrays.size(); ++ndx)
	{
		const ArraySpec& array = arrays[ndx];

		if (arrays.size() > 1)
			name << "array" << ndx << "_";

		name
			<< Array::storageToString(array.storage) << "_"
			<< array.offset << "_"
			<< array.stride << "_"
			<< Array::inputTypeToString((Array::InputType)array.inputType);
		if (array.inputType != Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 && array.inputType != Array::INPUTTYPE_INT_2_10_10_10)
			name << array.componentCount;
		name
			<< "_"
			<< (array.normalize ? "normalized_" : "")
			<< Array::outputTypeToString(array.outputType) << "_"
			<< Array::usageTypeToString(array.usage) << "_";
	}

	if (first)
		name << "first" << first << "_";

	switch (primitive)
	{
		case Array::PRIMITIVE_TRIANGLES:
			name << "quads_";
			break;
		case Array::PRIMITIVE_POINTS:
			name << "points_";
			break;

		default:
			DE_ASSERT(false);
			break;
	}

	name << drawCount;

	return name.str();
}

std::string MultiVertexArrayTest::Spec::getDesc (void) const
{
	std::stringstream desc;

	for (size_t ndx = 0; ndx < arrays.size(); ++ndx)
	{
		const ArraySpec& array = arrays[ndx];

		desc
			<< "Array " << ndx << ": "
			<< "Storage in " << Array::storageToString(array.storage) << ", "
			<< "stride " << array.stride << ", "
			<< "input datatype " << Array::inputTypeToString((Array::InputType)array.inputType) << ", "
			<< "input component count " << array.componentCount << ", "
			<< (array.normalize ? "normalized, " : "")
			<< "used as " << Array::outputTypeToString(array.outputType) << ", ";
	}

	desc
		<< "drawArrays(), "
		<< "first " << first << ", "
		<< drawCount;

	switch (primitive)
	{
		case Array::PRIMITIVE_TRIANGLES:
			desc << "quads ";
			break;
		case Array::PRIMITIVE_POINTS:
			desc << "points";
			break;

		default:
			DE_ASSERT(false);
			break;
	}


	return desc.str();
}

MultiVertexArrayTest::MultiVertexArrayTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const Spec& spec, const char* name, const char* desc)
	: VertexArrayTest	(testCtx, renderCtx, name, desc)
	, m_spec			(spec)
	, m_iteration		(0)
{
}

MultiVertexArrayTest::~MultiVertexArrayTest	(void)
{
}

MultiVertexArrayTest::IterateResult MultiVertexArrayTest::iterate (void)
{
	if (m_iteration == 0)
	{
		const size_t	primitiveSize		= (m_spec.primitive == Array::PRIMITIVE_TRIANGLES) ? (6) : (1); // in non-indexed draw Triangles means rectangles
		float			coordScale			= 1.0f;
		float			colorScale			= 1.0f;
		const bool		useVao				= m_renderCtx.getType().getProfile() == glu::PROFILE_CORE;

		// Log info
		m_testCtx.getLog() << TestLog::Message << m_spec.getDesc() << TestLog::EndMessage;

		// Color and Coord scale
		{
			// First array is always position
			{
				Spec::ArraySpec arraySpec = m_spec.arrays[0];
				if (arraySpec.inputType == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10)
				{
					if (arraySpec.normalize)
						coordScale = 1.0f;
					else
						coordScale = 1.0 / 1024.0;
				}
				else if (arraySpec.inputType == Array::INPUTTYPE_INT_2_10_10_10)
				{
					if (arraySpec.normalize)
						coordScale = 1.0f;
					else
						coordScale = 1.0 / 512.0;
				}
				else
					coordScale = (arraySpec.normalize && !inputTypeIsFloatType(arraySpec.inputType) ? 1.0f : float(0.9 / double(arraySpec.max.toFloat())));

				if (arraySpec.outputType == Array::OUTPUTTYPE_VEC3 || arraySpec.outputType == Array::OUTPUTTYPE_VEC4
					|| arraySpec.outputType == Array::OUTPUTTYPE_IVEC3 || arraySpec.outputType == Array::OUTPUTTYPE_IVEC4
					|| arraySpec.outputType == Array::OUTPUTTYPE_UVEC3 || arraySpec.outputType == Array::OUTPUTTYPE_UVEC4)
						coordScale = coordScale * 0.5f;
			}

			// And other arrays are color-like
			for (int arrayNdx = 1; arrayNdx < (int)m_spec.arrays.size(); arrayNdx++)
			{
				Spec::ArraySpec arraySpec	= m_spec.arrays[arrayNdx];

				colorScale *= (arraySpec.normalize && !inputTypeIsFloatType(arraySpec.inputType) ? 1.0f : float(1.0 / double(arraySpec.max.toFloat())));
				if (arraySpec.outputType == Array::OUTPUTTYPE_VEC4)
					colorScale *= (arraySpec.normalize && !inputTypeIsFloatType(arraySpec.inputType) ? 1.0f : float(1.0 / double(arraySpec.max.toFloat())));
			}
		}

		// Data
		for (int arrayNdx = 0; arrayNdx < (int)m_spec.arrays.size(); arrayNdx++)
		{
			Spec::ArraySpec arraySpec		= m_spec.arrays[arrayNdx];
			const int		seed			= int(arraySpec.inputType) + 10 * int(arraySpec.outputType) + 100 * int(arraySpec.storage) + 1000 * int(m_spec.primitive) + 10000 * int(arraySpec.usage) + int(m_spec.drawCount) + 12 * int(arraySpec.componentCount) + int(arraySpec.stride) + int(arraySpec.normalize);
			const char*		data			= DE_NULL;
			const size_t	stride			= (arraySpec.stride == 0) ? (arraySpec.componentCount * Array::inputTypeSize(arraySpec.inputType)) : (arraySpec.stride);
			const size_t	bufferSize		= arraySpec.offset + stride * (m_spec.drawCount * primitiveSize - 1) + arraySpec.componentCount  * Array::inputTypeSize(arraySpec.inputType);

			switch (m_spec.primitive)
			{
	//			case Array::PRIMITIVE_POINTS:
	//				data = RandomArrayGenerator::generateArray(seed, arraySpec.min, arraySpec.max, arraySpec.count, arraySpec.componentCount, arraySpec.stride, arraySpec.inputType);
	//				break;
				case Array::PRIMITIVE_TRIANGLES:
					if (arrayNdx == 0)
					{
						data = RandomArrayGenerator::generateQuads(seed, m_spec.drawCount, arraySpec.componentCount, arraySpec.offset, arraySpec.stride, m_spec.primitive, arraySpec.inputType, arraySpec.min, arraySpec.max);
					}
					else
					{
						DE_ASSERT(arraySpec.offset == 0); // \note [jarkko] it just hasn't been implemented
						data = RandomArrayGenerator::generatePerQuad(seed, m_spec.drawCount, arraySpec.componentCount, arraySpec.stride, m_spec.primitive, arraySpec.inputType, arraySpec.min, arraySpec.max);
					}
					break;

				default:
					DE_ASSERT(false);
					break;
			}

			m_glArrayPack->newArray(arraySpec.storage);
			m_rrArrayPack->newArray(arraySpec.storage);

			m_glArrayPack->getArray(arrayNdx)->data(Array::TARGET_ARRAY, (int)bufferSize, data, arraySpec.usage);
			m_rrArrayPack->getArray(arrayNdx)->data(Array::TARGET_ARRAY, (int)bufferSize, data, arraySpec.usage);

			m_glArrayPack->getArray(arrayNdx)->bind(arrayNdx, arraySpec.offset, arraySpec.componentCount, arraySpec.inputType, arraySpec.outputType, arraySpec.normalize, arraySpec.stride);
			m_rrArrayPack->getArray(arrayNdx)->bind(arrayNdx, arraySpec.offset, arraySpec.componentCount, arraySpec.inputType, arraySpec.outputType, arraySpec.normalize, arraySpec.stride);

			delete [] data;
		}

		try
		{
			m_glArrayPack->render(m_spec.primitive, m_spec.first, m_spec.drawCount * (int)primitiveSize, useVao, coordScale, colorScale);
			m_rrArrayPack->render(m_spec.primitive, m_spec.first, m_spec.drawCount * (int)primitiveSize, useVao, coordScale, colorScale);
		}
		catch (glu::Error& err)
		{
			// GL Errors are ok if the mode is not properly aligned

			m_testCtx.getLog() << TestLog::Message << "Got error: " << err.what() << TestLog::EndMessage;

			if (isUnalignedBufferOffsetTest())
				m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned buffers.");
			else if (isUnalignedBufferStrideTest())
				m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned stride.");
			else
				throw;

			return STOP;
		}

		m_iteration++;
		return CONTINUE;
	}
	else if (m_iteration == 1)
	{
		compare();

		if (m_isOk)
		{
			m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
		}
		else
		{
			if (isUnalignedBufferOffsetTest())
				m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned buffers.");
			else if (isUnalignedBufferStrideTest())
				m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned stride.");
			else
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed.");
		}

		m_iteration++;
		return STOP;
	}
	else
	{
		DE_ASSERT(false);
		return STOP;
	}
}

bool MultiVertexArrayTest::isUnalignedBufferOffsetTest (void) const
{
	// Buffer offsets should be data type size aligned
	for (size_t i = 0; i < m_spec.arrays.size(); ++i)
	{
		if (m_spec.arrays[i].storage == Array::STORAGE_BUFFER)
		{
			const bool inputTypePacked = m_spec.arrays[i].inputType == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_spec.arrays[i].inputType == Array::INPUTTYPE_INT_2_10_10_10;

			int dataTypeSize = Array::inputTypeSize(m_spec.arrays[i].inputType);
			if (inputTypePacked)
				dataTypeSize = 4;

			if (m_spec.arrays[i].offset % dataTypeSize != 0)
				return true;
		}
	}

	return false;
}

bool MultiVertexArrayTest::isUnalignedBufferStrideTest (void) const
{
	// Buffer strides should be data type size aligned
	for (size_t i = 0; i < m_spec.arrays.size(); ++i)
	{
		if (m_spec.arrays[i].storage == Array::STORAGE_BUFFER)
		{
			const bool inputTypePacked = m_spec.arrays[i].inputType == Array::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || m_spec.arrays[i].inputType == Array::INPUTTYPE_INT_2_10_10_10;

			int dataTypeSize = Array::inputTypeSize(m_spec.arrays[i].inputType);
			if (inputTypePacked)
				dataTypeSize = 4;

			if (m_spec.arrays[i].stride % dataTypeSize != 0)
				return true;
		}
	}

	return false;
}

} // gls
} // deqp