/*-------------------------------------------------------------------------
 * 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 Vertex texture tests.
 *//*--------------------------------------------------------------------*/

#include "es3fVertexTextureTests.hpp"
#include "glsTextureTestUtil.hpp"
#include "gluTexture.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "tcuVector.hpp"
#include "tcuMatrix.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "deRandom.hpp"
#include "deString.h"

#include <string>
#include <vector>

#include <limits>

#include "glw.h"

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

namespace deqp
{

using namespace gls::TextureTestUtil;

using gls::TextureTestUtil::TEXTURETYPE_2D;
using gls::TextureTestUtil::TEXTURETYPE_CUBE;
using gls::TextureTestUtil::TEXTURETYPE_2D_ARRAY;
using gls::TextureTestUtil::TEXTURETYPE_3D;

namespace gles3
{
namespace Functional
{

static const int WIDTH_2D_ARRAY				= 128;
static const int HEIGHT_2D_ARRAY			= 128;
static const int LAYERS_2D_ARRAY			= 8;

static const int WIDTH_3D					= 64;
static const int HEIGHT_3D					= 64;
static const int DEPTH_3D					= 64;

// The 2D case draws four images.
static const int MAX_2D_RENDER_WIDTH		= 128*2;
static const int MAX_2D_RENDER_HEIGHT		= 128*2;

// The cube map case draws four 3-by-2 image groups.
static const int MAX_CUBE_RENDER_WIDTH		= 28*2*3;
static const int MAX_CUBE_RENDER_HEIGHT		= 28*2*2;

static const int MAX_2D_ARRAY_RENDER_WIDTH	= 128*2;
static const int MAX_2D_ARRAY_RENDER_HEIGHT	= 128*2;

static const int MAX_3D_RENDER_WIDTH		= 128*2;
static const int MAX_3D_RENDER_HEIGHT		= 128*2;

static const int GRID_SIZE_2D				= 127;
static const int GRID_SIZE_CUBE				= 63;
static const int GRID_SIZE_2D_ARRAY			= 127;
static const int GRID_SIZE_3D				= 127;

// Helpers for making texture coordinates "safe", i.e. move them further from coordinate bounary.

// Moves x towards the closest K+targetFraction, where K is an integer.
// E.g. moveTowardsFraction(x, 0.5f) moves x away from integer boundaries.
static inline float moveTowardsFraction (float x, float targetFraction)
{
	const float strictness = 0.5f;
	DE_ASSERT(0.0f < strictness && strictness <= 1.0f);
	DE_ASSERT(de::inBounds(targetFraction, 0.0f, 1.0f));
	const float y = x + 0.5f - targetFraction;
	return deFloatFloor(y) + deFloatFrac(y)*(1.0f-strictness) + strictness*0.5f - 0.5f + targetFraction;
}

static inline float safeCoord (float raw, int scale, float fraction)
{
	const float scaleFloat = (float)scale;
	return moveTowardsFraction(raw*scaleFloat, fraction) / scaleFloat;
}

template <int Size>
static inline tcu::Vector<float, Size> safeCoords (const tcu::Vector<float, Size>& raw, const tcu::Vector<int, Size>& scale, const tcu::Vector<float, Size>& fraction)
{
	tcu::Vector<float, Size> result;
	for (int i = 0; i < Size; i++)
		result[i] = safeCoord(raw[i], scale[i], fraction[i]);
	return result;
}

static inline Vec2 safe2DTexCoords (const Vec2& raw, const IVec2& textureSize)
{
	return safeCoords(raw, textureSize, Vec2(0.5f));
}

static inline Vec3 safe2DArrayTexCoords (const Vec3& raw, const IVec3& textureSize)
{
	return safeCoords(raw, textureSize, Vec3(0.5f, 0.5f, 0.0f));
}

static inline Vec3 safe3DTexCoords (const Vec3& raw, const IVec3& textureSize)
{
	return safeCoords(raw, textureSize, Vec3(0.5f));
}

namespace
{

struct Rect
{
			Rect	(int x_, int y_, int w_, int h_) : x(x_), y(y_), w(w_), h(h_) {}
	IVec2	pos		(void) const { return IVec2(x, y); }
	IVec2	size	(void) const { return IVec2(w, h); }

	int		x;
	int		y;
	int		w;
	int		h;
};

template <TextureType> struct TexTypeTcuClass;
template <> struct TexTypeTcuClass<TEXTURETYPE_2D>			{ typedef tcu::Texture2D		t; };
template <> struct TexTypeTcuClass<TEXTURETYPE_CUBE>		{ typedef tcu::TextureCube		t; };
template <> struct TexTypeTcuClass<TEXTURETYPE_2D_ARRAY>	{ typedef tcu::Texture2DArray	t; };
template <> struct TexTypeTcuClass<TEXTURETYPE_3D>			{ typedef tcu::Texture3D		t; };

template <TextureType> struct TexTypeSizeDims;
template <> struct TexTypeSizeDims<TEXTURETYPE_2D>			{ enum { V = 2 }; };
template <> struct TexTypeSizeDims<TEXTURETYPE_CUBE>		{ enum { V = 2 }; };
template <> struct TexTypeSizeDims<TEXTURETYPE_2D_ARRAY>	{ enum { V = 3 }; };
template <> struct TexTypeSizeDims<TEXTURETYPE_3D>			{ enum { V = 3 }; };

template <TextureType> struct TexTypeCoordDims;
template <> struct TexTypeCoordDims<TEXTURETYPE_2D>			{ enum { V = 2 }; };
template <> struct TexTypeCoordDims<TEXTURETYPE_CUBE>		{ enum { V = 3 }; };
template <> struct TexTypeCoordDims<TEXTURETYPE_2D_ARRAY>	{ enum { V = 3 }; };
template <> struct TexTypeCoordDims<TEXTURETYPE_3D>			{ enum { V = 3 }; };

template <TextureType TexType> struct TexTypeSizeIVec		{ typedef tcu::Vector<int,		TexTypeSizeDims<TexType>::V>	t; };
template <TextureType TexType> struct TexTypeCoordVec		{ typedef tcu::Vector<float,	TexTypeCoordDims<TexType>::V>	t; };

template <TextureType> struct TexTypeCoordParams;

template <> struct
TexTypeCoordParams<TEXTURETYPE_2D>
{
	Vec2 scale;
	Vec2 bias;

	TexTypeCoordParams (const Vec2& scale_, const Vec2& bias_) : scale(scale_), bias(bias_) {}
};

template <> struct
TexTypeCoordParams<TEXTURETYPE_CUBE>
{
	Vec2			scale;
	Vec2			bias;
	tcu::CubeFace	face;

	TexTypeCoordParams (const Vec2& scale_, const Vec2& bias_, tcu::CubeFace face_) : scale(scale_), bias(bias_), face(face_) {}
};

template <> struct
TexTypeCoordParams<TEXTURETYPE_2D_ARRAY>
{
	Mat3 transform;

	TexTypeCoordParams (const Mat3& transform_) : transform(transform_) {}
};

template <> struct
TexTypeCoordParams<TEXTURETYPE_3D>
{
	Mat3 transform;

	TexTypeCoordParams (const Mat3& transform_) : transform(transform_) {}
};

/*--------------------------------------------------------------------*//*!
 * \brief Quad grid class containing position and texture coordinate data.
 *
 * A quad grid of size S means a grid consisting of S*S quads (S rows and
 * S columns). The quads are rectangles with main axis aligned sides, and
 * each consists of two triangles. Note that although there are only
 * (S+1)*(S+1) distinct vertex positions, there are S*S*4 distinct vertices
 * because we want texture coordinates to be constant across the vertices
 * of a quad (to avoid interpolation issues), and thus each quad needs its
 * own 4 vertices.
 *
 * Pointers returned by get*Ptr() are suitable for gl calls such as
 * glVertexAttribPointer() (for position and tex coord) or glDrawElements()
 * (for indices).
 *//*--------------------------------------------------------------------*/
template <TextureType TexType>
class PosTexCoordQuadGrid
{
private:
	enum { TEX_COORD_DIMS = TexTypeCoordDims <TexType>::V };
	typedef typename TexTypeCoordVec<TexType>::t	TexCoordVec;
	typedef typename TexTypeSizeIVec<TexType>::t	TexSizeIVec;
	typedef TexTypeCoordParams<TexType>				TexCoordParams;

public:
							PosTexCoordQuadGrid		(int gridSize, const IVec2& renderSize, const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords);

	int						getSize					(void) const { return m_gridSize; }
	Vec4					getQuadLDRU				(int col, int row) const; //!< Vec4(leftX, downY, rightX, upY)
	const TexCoordVec&		getQuadTexCoord			(int col, int row) const;

	int						getNumIndices			(void) const { return m_gridSize*m_gridSize*3*2; }
	const float*			getPositionPtr			(void) const { DE_STATIC_ASSERT(sizeof(Vec2) == 2*sizeof(float)); return (float*)&m_positions[0]; }
	const float*			getTexCoordPtr			(void) const { DE_STATIC_ASSERT(sizeof(TexCoordVec) == TEX_COORD_DIMS*(int)sizeof(float)); return (float*)&m_texCoords[0]; }
	const deUint16*			getIndexPtr				(void) const { return &m_indices[0]; }

private:
	void					initializeTexCoords		(const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords);

	const int				m_gridSize;
	vector<Vec2>			m_positions;
	vector<TexCoordVec>		m_texCoords;
	vector<deUint16>		m_indices;
};

template <TextureType TexType>
Vec4 PosTexCoordQuadGrid<TexType>::getQuadLDRU (int col, int row) const
{
	int ndx00 = (row*m_gridSize + col) * 4;
	int ndx11 = ndx00 + 3;

	return Vec4(m_positions[ndx00].x(),
				m_positions[ndx00].y(),
				m_positions[ndx11].x(),
				m_positions[ndx11].y());
}

template <TextureType TexType>
const typename TexTypeCoordVec<TexType>::t& PosTexCoordQuadGrid<TexType>::getQuadTexCoord (int col, int row) const
{
	return m_texCoords[(row*m_gridSize + col) * 4];
}

template <TextureType TexType>
PosTexCoordQuadGrid<TexType>::PosTexCoordQuadGrid (int gridSize, const IVec2& renderSize, const TexSizeIVec& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
	: m_gridSize(gridSize)
{
	DE_ASSERT(m_gridSize > 0 && m_gridSize*m_gridSize <= (int)std::numeric_limits<deUint16>::max() + 1);

	const float gridSizeFloat = (float)m_gridSize;

	m_positions.reserve(m_gridSize*m_gridSize*4);
	m_indices.reserve(m_gridSize*m_gridSize*3*2);

	for (int y = 0; y < m_gridSize; y++)
	for (int x = 0; x < m_gridSize; x++)
	{
		float fx0 = (float)(x+0) / gridSizeFloat;
		float fx1 = (float)(x+1) / gridSizeFloat;
		float fy0 = (float)(y+0) / gridSizeFloat;
		float fy1 = (float)(y+1) / gridSizeFloat;

		Vec2 quadVertices[4] = { Vec2(fx0, fy0), Vec2(fx1, fy0), Vec2(fx0, fy1), Vec2(fx1, fy1) };

		int firstNdx = (int)m_positions.size();

		for (int i = 0; i < DE_LENGTH_OF_ARRAY(quadVertices); i++)
			m_positions.push_back(safeCoords(quadVertices[i], renderSize, Vec2(0.0f)) * 2.0f - 1.0f);

		m_indices.push_back(firstNdx + 0);
		m_indices.push_back(firstNdx + 1);
		m_indices.push_back(firstNdx + 2);

		m_indices.push_back(firstNdx + 1);
		m_indices.push_back(firstNdx + 3);
		m_indices.push_back(firstNdx + 2);
	}

	m_texCoords.reserve(m_gridSize*m_gridSize*4);
	initializeTexCoords(textureSize, texCoordParams, useSafeTexCoords);

	DE_ASSERT((int)m_positions.size() == m_gridSize*m_gridSize*4);
	DE_ASSERT((int)m_indices.size() == m_gridSize*m_gridSize*3*2);
	DE_ASSERT((int)m_texCoords.size() == m_gridSize*m_gridSize*4);
}

template <>
void PosTexCoordQuadGrid<TEXTURETYPE_2D>::initializeTexCoords (const IVec2& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
{
	DE_ASSERT(m_texCoords.empty());

	const float gridSizeFloat = (float)m_gridSize;

	for (int y = 0; y < m_gridSize; y++)
	for (int x = 0; x < m_gridSize; x++)
	{
		Vec2 rawCoord = Vec2((float)x / gridSizeFloat, (float)y / gridSizeFloat) * texCoordParams.scale + texCoordParams.bias;

		for (int i = 0; i < 4; i++)
			m_texCoords.push_back(useSafeTexCoords ? safe2DTexCoords(rawCoord, textureSize) : rawCoord);
	}
}

template <>
void PosTexCoordQuadGrid<TEXTURETYPE_CUBE>::initializeTexCoords (const IVec2& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
{
	DE_ASSERT(m_texCoords.empty());

	const float		gridSizeFloat	= (float)m_gridSize;
	vector<float>	texBoundaries;
	computeQuadTexCoordCube(texBoundaries, texCoordParams.face);
	const Vec3		coordA			= Vec3(texBoundaries[0], texBoundaries[1], texBoundaries[2]);
	const Vec3		coordB			= Vec3(texBoundaries[3], texBoundaries[4], texBoundaries[5]);
	const Vec3		coordC			= Vec3(texBoundaries[6], texBoundaries[7], texBoundaries[8]);
	const Vec3		coordAB			= coordB - coordA;
	const Vec3		coordAC			= coordC - coordA;

	for (int y = 0; y < m_gridSize; y++)
	for (int x = 0; x < m_gridSize; x++)
	{
		const Vec2 rawFaceCoord		= texCoordParams.scale * Vec2((float)x / gridSizeFloat, (float)y / gridSizeFloat) + texCoordParams.bias;
		const Vec2 safeFaceCoord	= useSafeTexCoords ? safe2DTexCoords(rawFaceCoord, textureSize) : rawFaceCoord;
		const Vec3 texCoord			= coordA + coordAC*safeFaceCoord.x() + coordAB*safeFaceCoord.y();

		for (int i = 0; i < 4; i++)
			m_texCoords.push_back(texCoord);
	}
}

template <>
void PosTexCoordQuadGrid<TEXTURETYPE_2D_ARRAY>::initializeTexCoords (const IVec3& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
{
	DE_ASSERT(m_texCoords.empty());

	const float gridSizeFloat = (float)m_gridSize;

	for (int y = 0; y < m_gridSize; y++)
	for (int x = 0; x < m_gridSize; x++)
	{
		const Vec3 rawCoord = texCoordParams.transform * Vec3((float)x / gridSizeFloat, (float)y / gridSizeFloat, 1.0f);

		for (int i = 0; i < 4; i++)
			m_texCoords.push_back(useSafeTexCoords ? safe2DArrayTexCoords(rawCoord, textureSize) : rawCoord);
	}
}

template <>
void PosTexCoordQuadGrid<TEXTURETYPE_3D>::initializeTexCoords (const IVec3& textureSize, const TexCoordParams& texCoordParams, bool useSafeTexCoords)
{
	DE_ASSERT(m_texCoords.empty());

	const float gridSizeFloat = (float)m_gridSize;

	for (int y = 0; y < m_gridSize; y++)
	for (int x = 0; x < m_gridSize; x++)
	{
		Vec3 rawCoord = texCoordParams.transform * Vec3((float)x / gridSizeFloat, (float)y / gridSizeFloat, 1.0f);

		for (int i = 0; i < 4; i++)
			m_texCoords.push_back(useSafeTexCoords ? safe3DTexCoords(rawCoord, textureSize) : rawCoord);
	}
}

} // anonymous

static inline bool isLevelNearest (deUint32 filter)
{
	return filter == GL_NEAREST || filter == GL_NEAREST_MIPMAP_NEAREST || filter == GL_NEAREST_MIPMAP_LINEAR;
}

static inline IVec2 getTextureSize (const glu::Texture2D& tex)
{
	const tcu::Texture2D& ref = tex.getRefTexture();
	return IVec2(ref.getWidth(), ref.getHeight());
}

static inline IVec2 getTextureSize (const glu::TextureCube& tex)
{
	const tcu::TextureCube& ref = tex.getRefTexture();
	return IVec2(ref.getSize(), ref.getSize());
}

static inline IVec3 getTextureSize (const glu::Texture2DArray& tex)
{
	const tcu::Texture2DArray& ref = tex.getRefTexture();
	return IVec3(ref.getWidth(), ref.getHeight(), ref.getNumLayers());
}

static inline IVec3 getTextureSize (const glu::Texture3D& tex)
{
	const tcu::Texture3D& ref = tex.getRefTexture();
	return IVec3(ref.getWidth(), ref.getHeight(), ref.getDepth());
}

template <TextureType TexType>
static void setPixelColors (const vector<Vec4>& quadColors, const Rect& region, const PosTexCoordQuadGrid<TexType>& grid, tcu::Surface& dst)
{
	const int gridSize = grid.getSize();

	for (int y = 0; y < gridSize; y++)
	for (int x = 0; x < gridSize; x++)
	{
		const Vec4	color	= quadColors[y*gridSize + x];
		const Vec4	ldru	= grid.getQuadLDRU(x, y) * 0.5f + 0.5f; // [-1, 1] -> [0, 1]
		const int	ix0		= deCeilFloatToInt32(ldru.x() * (float)region.w - 0.5f);
		const int	ix1		= deCeilFloatToInt32(ldru.z() * (float)region.w - 0.5f);
		const int	iy0		= deCeilFloatToInt32(ldru.y() * (float)region.h - 0.5f);
		const int	iy1		= deCeilFloatToInt32(ldru.w() * (float)region.h - 0.5f);

		for (int iy = iy0; iy < iy1; iy++)
		for (int ix = ix0; ix < ix1; ix++)
		{
			DE_ASSERT(deInBounds32(ix + region.x, 0, dst.getWidth()));
			DE_ASSERT(deInBounds32(iy + region.y, 0, dst.getHeight()));

			dst.setPixel(ix + region.x, iy + region.y, toRGBA(color));
		}
	}
}

static inline Vec4 sample (const tcu::Texture2D&		tex, const Vec2& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), lod); }
static inline Vec4 sample (const tcu::TextureCube&		tex, const Vec3& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), coord.z(), lod); }
static inline Vec4 sample (const tcu::Texture2DArray&	tex, const Vec3& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), coord.z(), lod); }
static inline Vec4 sample (const tcu::Texture3D&		tex, const Vec3& coord, float lod, const tcu::Sampler& sam) { return tex.sample(sam, coord.x(), coord.y(), coord.z(), lod); }

template <TextureType TexType>
void computeReference (const typename TexTypeTcuClass<TexType>::t& texture, float lod, const tcu::Sampler& sampler, const PosTexCoordQuadGrid<TexType>& grid, tcu::Surface& dst, const Rect& dstRegion)
{
	const int		gridSize	= grid.getSize();
	vector<Vec4>	quadColors	(gridSize*gridSize);

	for (int y = 0; y < gridSize; y++)
	for (int x = 0; x < gridSize; x++)
	{
		const int										ndx		= y*gridSize + x;
		const typename TexTypeCoordVec<TexType>::t&		coord	= grid.getQuadTexCoord(x, y);

		quadColors[ndx] = sample(texture, coord, lod, sampler);
	}

	setPixelColors(quadColors, dstRegion, grid, dst);
}

static bool compareImages (const glu::RenderContext& renderCtx, tcu::TestLog& log, const tcu::Surface& ref, const tcu::Surface& res)
{
	DE_ASSERT(renderCtx.getRenderTarget().getNumSamples() == 0);

	const tcu::RGBA threshold = renderCtx.getRenderTarget().getPixelFormat().getColorThreshold() + tcu::RGBA(15,15,15,15);
	return tcu::pixelThresholdCompare(log, "Result", "Image compare result", ref, res, threshold, tcu::COMPARE_LOG_RESULT);
}

class Vertex2DTextureCase : public TestCase
{
public:
								Vertex2DTextureCase		(Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT);
								~Vertex2DTextureCase	(void);

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

private:
	typedef PosTexCoordQuadGrid<TEXTURETYPE_2D> Grid;

								Vertex2DTextureCase		(const Vertex2DTextureCase& other);
	Vertex2DTextureCase&		operator=				(const Vertex2DTextureCase& other);

	float						calculateLod			(const Vec2& texScale, const Vec2& dstSize, int textureNdx) const;
	void						setupShaderInputs		(int textureNdx, float lod, const Grid& grid) const;
	void						renderCell				(int textureNdx, float lod, const Grid& grid) const;
	void						computeReferenceCell	(int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;

	const deUint32				m_minFilter;
	const deUint32				m_magFilter;
	const deUint32				m_wrapS;
	const deUint32				m_wrapT;

	const glu::ShaderProgram*	m_program;
	glu::Texture2D*				m_textures[2];	// 2 textures, a gradient texture and a grid texture.
};

Vertex2DTextureCase::Vertex2DTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT)
	: TestCase				(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
	, m_minFilter			(minFilter)
	, m_magFilter			(magFilter)
	, m_wrapS				(wrapS)
	, m_wrapT				(wrapT)
	, m_program				(DE_NULL)
{
	m_textures[0] = DE_NULL;
	m_textures[1] = DE_NULL;
}

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

void Vertex2DTextureCase::init (void)
{
	const char* const vertexShader =
		"#version 300 es\n"
		"in highp vec2 a_position;\n"
		"in highp vec2 a_texCoord;\n"
		"uniform highp sampler2D u_texture;\n"
		"uniform highp float u_lod;\n"
		"out mediump vec4 v_color;\n"
		"\n"
		"void main()\n"
		"{\n"
		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
		"	v_color = textureLod(u_texture, a_texCoord, u_lod);\n"
		"}\n";

	const char* const fragmentShader =
		"#version 300 es\n"
		"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
		"in mediump vec4 v_color;\n"
		"\n"
		"void main()\n"
		"{\n"
		"	dEQP_FragColor = v_color;\n"
		"}\n";

	if (m_context.getRenderTarget().getNumSamples() != 0)
		throw tcu::NotSupportedError("MSAA config not supported by this test");

	DE_ASSERT(!m_program);
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShader, fragmentShader));

	if(!m_program->isOk())
	{
		m_testCtx.getLog() << *m_program;

		GLint maxVertexTextures;
		glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);

		if (maxVertexTextures < 1)
			throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
		else
			TCU_FAIL("Failed to compile shader");
	}

	// Make the textures.
	try
	{
		// Compute suitable power-of-two sizes (for mipmaps).
		const int texWidth		= 1 << deLog2Ceil32(MAX_2D_RENDER_WIDTH / 2);
		const int texHeight		= 1 << deLog2Ceil32(MAX_2D_RENDER_HEIGHT / 2);

		for (int i = 0; i < 2; i++)
		{
			DE_ASSERT(!m_textures[i]);
			m_textures[i] = new glu::Texture2D(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight);
		}

		const bool						mipmaps		= (deIsPowerOfTwo32(texWidth) && deIsPowerOfTwo32(texHeight));
		const int						numLevels	= mipmaps ? deLog2Floor32(de::max(texWidth, texHeight))+1 : 1;
		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
		const Vec4						cBias		= fmtInfo.valueMin;
		const Vec4						cScale		= fmtInfo.valueMax-fmtInfo.valueMin;

		// Fill first with gradient texture.
		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
		{
			const Vec4 gMin = Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
			const Vec4 gMax = Vec4( 1.0f,  1.0f,  1.0f, 0.0f)*cScale + cBias;

			m_textures[0]->getRefTexture().allocLevel(levelNdx);
			tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
		}

		// Fill second with grid texture.
		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
		{
			const deUint32 step		= 0x00ffffff / numLevels;
			const deUint32 rgb		= step*levelNdx;
			const deUint32 colorA	= 0xff000000 | rgb;
			const deUint32 colorB	= 0xff000000 | ~rgb;

			m_textures[1]->getRefTexture().allocLevel(levelNdx);
			tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
		}

		// Upload.
		for (int i = 0; i < 2; i++)
			m_textures[i]->upload();
	}
	catch (const std::exception&)
	{
		// Clean up to save memory.
		Vertex2DTextureCase::deinit();
		throw;
	}
}

void Vertex2DTextureCase::deinit (void)
{
	for (int i = 0; i < 2; i++)
	{
		delete m_textures[i];
		m_textures[i] = DE_NULL;
	}

	delete m_program;
	m_program = DE_NULL;
}

float Vertex2DTextureCase::calculateLod (const Vec2& texScale, const Vec2& dstSize, int textureNdx) const
{
	const tcu::Texture2D&		refTexture	= m_textures[textureNdx]->getRefTexture();
	const Vec2					srcSize		= Vec2((float)refTexture.getWidth(), (float)refTexture.getHeight());
	const Vec2					sizeRatio	= texScale*srcSize / dstSize;

	// \note In this particular case dv/dx and du/dy are zero, simplifying the expression.
	return deFloatLog2(de::max(sizeRatio.x(), sizeRatio.y()));
}

Vertex2DTextureCase::IterateResult Vertex2DTextureCase::iterate (void)
{
	const int	viewportWidth		= deMin32(m_context.getRenderTarget().getWidth(), MAX_2D_RENDER_WIDTH);
	const int	viewportHeight		= deMin32(m_context.getRenderTarget().getHeight(), MAX_2D_RENDER_HEIGHT);

	const int	viewportXOffsetMax	= m_context.getRenderTarget().getWidth() - viewportWidth;
	const int	viewportYOffsetMax	= m_context.getRenderTarget().getHeight() - viewportHeight;

	de::Random	rnd					(deStringHash(getName()));

	const int	viewportXOffset		= rnd.getInt(0, viewportXOffsetMax);
	const int	viewportYOffset		= rnd.getInt(0, viewportYOffsetMax);

	glUseProgram(m_program->getProgram());

	// Divide viewport into 4 cells.
	const int leftWidth		= viewportWidth / 2;
	const int rightWidth	= viewportWidth - leftWidth;
	const int bottomHeight	= viewportHeight / 2;
	const int topHeight		= viewportHeight - bottomHeight;

	// Clear.
	glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	// Texture scaling and offsetting vectors.
	const Vec2 texMinScale		(+1.8f, +1.8f);
	const Vec2 texMinOffset		(-0.3f, -0.2f);
	const Vec2 texMagScale		(+0.3f, +0.3f);
	const Vec2 texMagOffset		(+0.9f, +0.8f);

	// Surface for the reference image.
	tcu::Surface refImage(viewportWidth, viewportHeight);

	{
		const struct Render
		{
			const Rect	region;
			int			textureNdx;
			const Vec2	texCoordScale;
			const Vec2	texCoordOffset;
			Render (const Rect& r, int tN, const Vec2& tS, const Vec2& tO) : region(r), textureNdx(tN), texCoordScale(tS), texCoordOffset(tO) {}
		} renders[] =
		{
			Render(Rect(0,				0,				leftWidth,	bottomHeight),	0, texMinScale, texMinOffset),
			Render(Rect(leftWidth,		0,				rightWidth,	bottomHeight),	0, texMagScale, texMagOffset),
			Render(Rect(0,				bottomHeight,	leftWidth,	topHeight),		1, texMinScale, texMinOffset),
			Render(Rect(leftWidth,		bottomHeight,	rightWidth,	topHeight),		1, texMagScale, texMagOffset)
		};

		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
		{
			const Render&	rend				= renders[renderNdx];
			const float		lod					= calculateLod(rend.texCoordScale, rend.region.size().asFloat(), rend.textureNdx);
			const bool		useSafeTexCoords	= isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
			const Grid		grid				(GRID_SIZE_2D, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
												 TexTypeCoordParams<TEXTURETYPE_2D>(rend.texCoordScale, rend.texCoordOffset), useSafeTexCoords);

			glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
			renderCell				(rend.textureNdx, lod, grid);
			computeReferenceCell	(rend.textureNdx, lod, grid, refImage, rend.region);
		}
	}

	// Read back rendered results.
	tcu::Surface resImage(viewportWidth, viewportHeight);
	glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());

	glUseProgram(0);

	// Compare and log.
	{
		const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);

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

	return STOP;
}

void Vertex2DTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
{
	const deUint32 programID = m_program->getProgram();

	// SETUP ATTRIBUTES.

	{
		const int positionLoc = glGetAttribLocation(programID, "a_position");
		if (positionLoc != -1)
		{
			glEnableVertexAttribArray(positionLoc);
			glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
		}
	}

	{
		const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
		if (texCoordLoc != -1)
		{
			glEnableVertexAttribArray(texCoordLoc);
			glVertexAttribPointer(texCoordLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
		}
	}

	// SETUP UNIFORMS.

	{
		const int lodLoc = glGetUniformLocation(programID, "u_lod");
		if (lodLoc != -1)
			glUniform1f(lodLoc, lod);
	}

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, m_textures[textureNdx]->getGLTexture());
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,		m_wrapS);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,		m_wrapT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,	m_magFilter);

	{
		const int texLoc = glGetUniformLocation(programID, "u_texture");
		if (texLoc != -1)
			glUniform1i(texLoc, 0);
	}
}

// Renders one sub-image with given parameters.
void Vertex2DTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
{
	setupShaderInputs(textureNdx, lod, grid);
	glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
}

void Vertex2DTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
{
	computeReference(m_textures[textureNdx]->getRefTexture(), lod, glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter), grid, dst, dstRegion);
}

class VertexCubeTextureCase : public TestCase
{
public:
								VertexCubeTextureCase	(Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT);
								~VertexCubeTextureCase	(void);

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

private:
	typedef PosTexCoordQuadGrid<TEXTURETYPE_CUBE> Grid;

								VertexCubeTextureCase	(const VertexCubeTextureCase& other);
	VertexCubeTextureCase&		operator=				(const VertexCubeTextureCase& other);

	float						calculateLod			(const Vec2& texScale, const Vec2& dstSize, int textureNdx) const;
	void						setupShaderInputs		(int textureNdx, float lod, const Grid& grid) const;
	void						renderCell				(int textureNdx, float lod, const Grid& grid) const;
	void						computeReferenceCell	(int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;

	const deUint32				m_minFilter;
	const deUint32				m_magFilter;
	const deUint32				m_wrapS;
	const deUint32				m_wrapT;

	const glu::ShaderProgram*	m_program;
	glu::TextureCube*			m_textures[2];	// 2 textures, a gradient texture and a grid texture.
};

VertexCubeTextureCase::VertexCubeTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT)
	: TestCase				(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
	, m_minFilter			(minFilter)
	, m_magFilter			(magFilter)
	, m_wrapS				(wrapS)
	, m_wrapT				(wrapT)
	, m_program				(DE_NULL)
{
	m_textures[0] = DE_NULL;
	m_textures[1] = DE_NULL;
}

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

void VertexCubeTextureCase::init (void)
{
	const char* const vertexShader =
		"#version 300 es\n"
		"in highp vec2 a_position;\n"
		"in highp vec3 a_texCoord;\n"
		"uniform highp samplerCube u_texture;\n"
		"uniform highp float u_lod;\n"
		"out mediump vec4 v_color;\n"
		"\n"
		"void main()\n"
		"{\n"
		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
		"	v_color = textureLod(u_texture, a_texCoord, u_lod);\n"
		"}\n";

	const char* const fragmentShader =
		"#version 300 es\n"
		"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
		"in mediump vec4 v_color;\n"
		"\n"
		"void main()\n"
		"{\n"
		"	dEQP_FragColor = v_color;\n"
		"}\n";

	if (m_context.getRenderTarget().getNumSamples() != 0)
		throw tcu::NotSupportedError("MSAA config not supported by this test");

	DE_ASSERT(!m_program);
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShader, fragmentShader));

	if(!m_program->isOk())
	{
		m_testCtx.getLog() << *m_program;

		GLint maxVertexTextures;
		glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);

		if (maxVertexTextures < 1)
			throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
		else
			TCU_FAIL("Failed to compile shader");
	}

	// Make the textures.
	try
	{
		// Compute suitable power-of-two sizes (for mipmaps).
		const int texWidth		= 1 << deLog2Ceil32(MAX_CUBE_RENDER_WIDTH / 3 / 2);
		const int texHeight		= 1 << deLog2Ceil32(MAX_CUBE_RENDER_HEIGHT / 2 / 2);

		DE_ASSERT(texWidth == texHeight);
		DE_UNREF(texHeight);

		for (int i = 0; i < 2; i++)
		{
			DE_ASSERT(!m_textures[i]);
			m_textures[i] = new glu::TextureCube(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth);
		}

		const bool						mipmaps		= deIsPowerOfTwo32(texWidth) != DE_FALSE;
		const int						numLevels	= mipmaps ? deLog2Floor32(texWidth)+1 : 1;
		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
		const Vec4						cBias		= fmtInfo.valueMin;
		const Vec4						cScale		= fmtInfo.valueMax-fmtInfo.valueMin;

		// Fill first with gradient texture.
		static const Vec4 gradients[tcu::CUBEFACE_LAST][2] =
		{
			{ Vec4(-1.0f, -1.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative x
			{ Vec4( 0.0f, -1.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive x
			{ Vec4(-1.0f,  0.0f, -1.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // negative y
			{ Vec4(-1.0f, -1.0f,  0.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }, // positive y
			{ Vec4(-1.0f, -1.0f, -1.0f, 0.0f), Vec4(1.0f, 1.0f, 1.0f, 1.0f) }, // negative z
			{ Vec4( 0.0f,  0.0f,  0.0f, 2.0f), Vec4(1.0f, 1.0f, 1.0f, 0.0f) }  // positive z
		};
		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
		{
			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
			{
				m_textures[0]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
				tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), gradients[face][0]*cScale + cBias, gradients[face][1]*cScale + cBias);
			}
		}

		// Fill second with grid texture.
		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
		{
			for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
			{
				const deUint32 step		= 0x00ffffff / (numLevels*tcu::CUBEFACE_LAST);
				const deUint32 rgb		= step*levelNdx*face;
				const deUint32 colorA	= 0xff000000 | rgb;
				const deUint32 colorB	= 0xff000000 | ~rgb;

				m_textures[1]->getRefTexture().allocLevel((tcu::CubeFace)face, levelNdx);
				tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevelFace(levelNdx, (tcu::CubeFace)face), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
			}
		}

		// Upload.
		for (int i = 0; i < 2; i++)
			m_textures[i]->upload();
	}
	catch (const std::exception&)
	{
		// Clean up to save memory.
		VertexCubeTextureCase::deinit();
		throw;
	}
}

void VertexCubeTextureCase::deinit (void)
{
	for (int i = 0; i < 2; i++)
	{
		delete m_textures[i];
		m_textures[i] = DE_NULL;
	}

	delete m_program;
	m_program = DE_NULL;
}

float VertexCubeTextureCase::calculateLod (const Vec2& texScale, const Vec2& dstSize, int textureNdx) const
{
	const tcu::TextureCube&		refTexture	= m_textures[textureNdx]->getRefTexture();
	const Vec2					srcSize		= Vec2((float)refTexture.getSize(), (float)refTexture.getSize());
	const Vec2					sizeRatio	= texScale*srcSize / dstSize;

	// \note In this particular case, dv/dx and du/dy are zero, simplifying the expression.
	return deFloatLog2(de::max(sizeRatio.x(), sizeRatio.y()));
}

VertexCubeTextureCase::IterateResult VertexCubeTextureCase::iterate (void)
{
	const int	viewportWidth		= deMin32(m_context.getRenderTarget().getWidth(), MAX_CUBE_RENDER_WIDTH);
	const int	viewportHeight		= deMin32(m_context.getRenderTarget().getHeight(), MAX_CUBE_RENDER_HEIGHT);

	const int	viewportXOffsetMax	= m_context.getRenderTarget().getWidth() - viewportWidth;
	const int	viewportYOffsetMax	= m_context.getRenderTarget().getHeight() - viewportHeight;

	de::Random	rnd					(deStringHash(getName()));

	const int	viewportXOffset		= rnd.getInt(0, viewportXOffsetMax);
	const int	viewportYOffset		= rnd.getInt(0, viewportYOffsetMax);

	glUseProgram(m_program->getProgram());

	// Divide viewport into 4 areas.
	const int leftWidth		= viewportWidth / 2;
	const int rightWidth	= viewportWidth - leftWidth;
	const int bottomHeight	= viewportHeight / 2;
	const int topHeight		= viewportHeight - bottomHeight;

	// Clear.
	glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	// Texture scaling and offsetting vectors.
	const Vec2 texMinScale		(1.0f, 1.0f);
	const Vec2 texMinOffset		(0.0f, 0.0f);
	const Vec2 texMagScale		(0.3f, 0.3f);
	const Vec2 texMagOffset		(0.5f, 0.3f);

	// Surface for the reference image.
	tcu::Surface refImage(viewportWidth, viewportHeight);

	// Each of the four areas is divided into 6 cells.
	const int defCellWidth	= viewportWidth / 2 / 3;
	const int defCellHeight	= viewportHeight / 2 / 2;

	for (int i = 0; i < tcu::CUBEFACE_LAST; i++)
	{
		const int	cellOffsetX			= defCellWidth * (i % 3);
		const int	cellOffsetY			= defCellHeight * (i / 3);
		const bool	isRightmostCell		= i == 2 || i == 5;
		const bool	isTopCell			= i >= 3;
		const int	leftCellWidth		= isRightmostCell	? leftWidth		- cellOffsetX : defCellWidth;
		const int	rightCellWidth		= isRightmostCell	? rightWidth	- cellOffsetX : defCellWidth;
		const int	bottomCellHeight	= isTopCell			? bottomHeight	- cellOffsetY : defCellHeight;
		const int	topCellHeight		= isTopCell			? topHeight		- cellOffsetY : defCellHeight;

		const struct Render
		{
			const Rect	region;
			int			textureNdx;
			const Vec2	texCoordScale;
			const Vec2	texCoordOffset;
			Render (const Rect& r, int tN, const Vec2& tS, const Vec2& tO) : region(r), textureNdx(tN), texCoordScale(tS), texCoordOffset(tO) {}
		} renders[] =
		{
			Render(Rect(cellOffsetX + 0,			cellOffsetY + 0,				leftCellWidth,	bottomCellHeight),	0, texMinScale, texMinOffset),
			Render(Rect(cellOffsetX + leftWidth,	cellOffsetY + 0,				rightCellWidth,	bottomCellHeight),	0, texMagScale, texMagOffset),
			Render(Rect(cellOffsetX + 0,			cellOffsetY + bottomHeight,		leftCellWidth,	topCellHeight),		1, texMinScale, texMinOffset),
			Render(Rect(cellOffsetX + leftWidth,	cellOffsetY + bottomHeight,		rightCellWidth,	topCellHeight),		1, texMagScale, texMagOffset)
		};

		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
		{
			const Render&	rend				= renders[renderNdx];
			const float		lod					= calculateLod(rend.texCoordScale, rend.region.size().asFloat(), rend.textureNdx);
			const bool		useSafeTexCoords	= isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
			const Grid		grid				(GRID_SIZE_CUBE, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
												 TexTypeCoordParams<TEXTURETYPE_CUBE>(rend.texCoordScale, rend.texCoordOffset, (tcu::CubeFace)i), useSafeTexCoords);

			glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
			renderCell				(rend.textureNdx, lod, grid);
			computeReferenceCell	(rend.textureNdx, lod, grid, refImage, rend.region);
		}
	}

	// Read back rendered results.
	tcu::Surface resImage(viewportWidth, viewportHeight);
	glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());

	glUseProgram(0);

	// Compare and log.
	{
		const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);

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

	return STOP;
}

void VertexCubeTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
{
	const deUint32 programID = m_program->getProgram();

	// SETUP ATTRIBUTES.

	{
		const int positionLoc = glGetAttribLocation(programID, "a_position");
		if (positionLoc != -1)
		{
			glEnableVertexAttribArray(positionLoc);
			glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
		}
	}

	{
		const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
		if (texCoordLoc != -1)
		{
			glEnableVertexAttribArray(texCoordLoc);
			glVertexAttribPointer(texCoordLoc, 3, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
		}
	}

	// SETUP UNIFORMS.

	{
		const int lodLoc = glGetUniformLocation(programID, "u_lod");
		if (lodLoc != -1)
			glUniform1f(lodLoc, lod);
	}

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_CUBE_MAP, m_textures[textureNdx]->getGLTexture());
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S,		m_wrapS);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T,		m_wrapT);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	m_minFilter);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,	m_magFilter);

	{
		const int texLoc = glGetUniformLocation(programID, "u_texture");
		if (texLoc != -1)
			glUniform1i(texLoc, 0);
	}
}

// Renders one cube face with given parameters.
void VertexCubeTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
{
	setupShaderInputs(textureNdx, lod, grid);
	glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
}

// Computes reference for one cube face with given parameters.
void VertexCubeTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
{
	tcu::Sampler sampler = glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter);
	sampler.seamlessCubeMap = true;
	computeReference(m_textures[textureNdx]->getRefTexture(), lod, sampler, grid, dst, dstRegion);
}

class Vertex2DArrayTextureCase : public TestCase
{
public:
								Vertex2DArrayTextureCase	(Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT);
								~Vertex2DArrayTextureCase	(void);

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

private:
	typedef PosTexCoordQuadGrid<TEXTURETYPE_2D_ARRAY> Grid;

								Vertex2DArrayTextureCase	(const Vertex2DArrayTextureCase& other);
	Vertex2DArrayTextureCase&	operator=					(const Vertex2DArrayTextureCase& other);

	float						calculateLod				(const Mat3& transf, const Vec2& dstSize, int textureNdx) const;
	void						setupShaderInputs			(int textureNdx, float lod, const Grid& grid) const;
	void						renderCell					(int textureNdx, float lod, const Grid& grid) const;
	void						computeReferenceCell		(int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;

	const deUint32				m_minFilter;
	const deUint32				m_magFilter;
	const deUint32				m_wrapS;
	const deUint32				m_wrapT;

	const glu::ShaderProgram*	m_program;
	glu::Texture2DArray*		m_textures[2];	// 2 textures, a gradient texture and a grid texture.
};

Vertex2DArrayTextureCase::Vertex2DArrayTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT)
	: TestCase				(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
	, m_minFilter			(minFilter)
	, m_magFilter			(magFilter)
	, m_wrapS				(wrapS)
	, m_wrapT				(wrapT)
	, m_program				(DE_NULL)
{
	m_textures[0] = DE_NULL;
	m_textures[1] = DE_NULL;
}

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

void Vertex2DArrayTextureCase::init (void)
{
	const char* const vertexShaderSource =
		"#version 300 es\n"
		"in highp vec2 a_position;\n"
		"in highp vec3 a_texCoord;\n"
		"uniform highp sampler2DArray u_texture;\n"
		"uniform highp float u_lod;\n"
		"out mediump vec4 v_color;\n"
		"\n"
		"void main()\n"
		"{\n"
		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
		"	v_color = textureLod(u_texture, a_texCoord, u_lod);\n"
		"}\n";

	const char* const fragmentShaderSource =
		"#version 300 es\n"
		"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
		"in mediump vec4 v_color;\n"
		"\n"
		"void main()\n"
		"{\n"
		"	dEQP_FragColor = v_color;\n"
		"}\n";

	if (m_context.getRenderTarget().getNumSamples() != 0)
		throw tcu::NotSupportedError("MSAA config not supported by this test");

	// Create shader.

	DE_ASSERT(!m_program);
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));

	if(!m_program->isOk())
	{
		m_testCtx.getLog() << *m_program;

		GLint maxVertexTextures;
		glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);

		if (maxVertexTextures < 1)
			throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
		else
			TCU_FAIL("Failed to compile shader");
	}

	// Make the textures.

	try
	{
		const int texWidth	= WIDTH_2D_ARRAY;
		const int texHeight	= HEIGHT_2D_ARRAY;
		const int texLayers	= LAYERS_2D_ARRAY;

		for (int i = 0; i < 2; i++)
		{
			DE_ASSERT(!m_textures[i]);
			m_textures[i] = new glu::Texture2DArray(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight, texLayers);
		}

		const int						numLevels	= deLog2Floor32(de::max(texWidth, texHeight)) + 1;
		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
		const Vec4						cBias		= fmtInfo.valueMin;
		const Vec4						cScale		= fmtInfo.valueMax-fmtInfo.valueMin;

		// Fill first with gradient texture.
		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
		{
			const Vec4 gMin = Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
			const Vec4 gMax = Vec4( 1.0f,  1.0f,  1.0f, 0.0f)*cScale + cBias;

			m_textures[0]->getRefTexture().allocLevel(levelNdx);
			tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
		}

		// Fill second with grid texture.
		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
		{
			const deUint32 step		= 0x00ffffff / numLevels;
			const deUint32 rgb		= step*levelNdx;
			const deUint32 colorA	= 0xff000000 | rgb;
			const deUint32 colorB	= 0xff000000 | ~rgb;

			m_textures[1]->getRefTexture().allocLevel(levelNdx);
			tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
		}

		// Upload.
		for (int i = 0; i < 2; i++)
			m_textures[i]->upload();
	}
	catch (const std::exception&)
	{
		// Clean up to save memory.
		Vertex2DArrayTextureCase::deinit();
		throw;
	}
}

void Vertex2DArrayTextureCase::deinit (void)
{
	for (int i = 0; i < 2; i++)
	{
		delete m_textures[i];
		m_textures[i] = DE_NULL;
	}

	delete m_program;
	m_program = DE_NULL;
}

float Vertex2DArrayTextureCase::calculateLod (const Mat3& transf, const Vec2& dstSize, int textureNdx) const
{
	const tcu::Texture2DArray&	refTexture	= m_textures[textureNdx]->getRefTexture();
	const int					texWidth	= refTexture.getWidth();
	const int					texHeight	= refTexture.getHeight();

	// Calculate transformed coordinates of three screen corners.
	const Vec2					trans00		= (transf * Vec3(0.0f, 0.0f, 1.0f)).xy();
	const Vec2					trans01		= (transf * Vec3(0.0f, 1.0f, 1.0f)).xy();
	const Vec2					trans10		= (transf * Vec3(1.0f, 0.0f, 1.0f)).xy();

	// Derivates.
	const float dudx = (trans10.x() - trans00.x()) * (float)texWidth / dstSize.x();
	const float dudy = (trans01.x() - trans00.x()) * (float)texWidth / dstSize.y();
	const float dvdx = (trans10.y() - trans00.y()) * (float)texHeight / dstSize.x();
	const float dvdy = (trans01.y() - trans00.y()) * (float)texHeight / dstSize.y();

	return deFloatLog2(deFloatSqrt(de::max(dudx*dudx + dvdx*dvdx, dudy*dudy + dvdy*dvdy)));
}

Vertex2DArrayTextureCase::IterateResult Vertex2DArrayTextureCase::iterate (void)
{
	const int	viewportWidth		= deMin32(m_context.getRenderTarget().getWidth(), MAX_2D_ARRAY_RENDER_WIDTH);
	const int	viewportHeight		= deMin32(m_context.getRenderTarget().getHeight(), MAX_2D_ARRAY_RENDER_HEIGHT);

	const int	viewportXOffsetMax	= m_context.getRenderTarget().getWidth() - viewportWidth;
	const int	viewportYOffsetMax	= m_context.getRenderTarget().getHeight() - viewportHeight;

	de::Random	rnd					(deStringHash(getName()));

	const int	viewportXOffset		= rnd.getInt(0, viewportXOffsetMax);
	const int	viewportYOffset		= rnd.getInt(0, viewportYOffsetMax);

	glUseProgram(m_program->getProgram());

	// Divide viewport into 4 cells.
	const int leftWidth		= viewportWidth / 2;
	const int rightWidth	= viewportWidth - leftWidth;
	const int bottomHeight	= viewportHeight / 2;
	const int topHeight		= viewportHeight - bottomHeight;

	// Clear.
	glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	// Shear by layer count to get all layers visible.
	static const float layerShearTransfData[] =
	{
		1.0f,					0.0f, 0.0f,
		0.0f,					1.0f, 0.0f,
		(float)LAYERS_2D_ARRAY, 0.0f, 0.0f
	};

	// Minification and magnification transformations.
	static const float texMinTransfData[] =
	{
		2.1f,  0.0f, -0.3f,
		0.0f,  2.1f, -0.3f,
		0.0f,  0.0f,  1.0f
	};
	static const float texMagTransfData[] =
	{
		0.4f,  0.0f,  0.8f,
		0.0f,  0.4f,  0.8f,
		0.0f,  0.0f,  1.0f
	};

	// Transformation matrices for minification and magnification.
	const Mat3 texMinTransf = Mat3(layerShearTransfData) * Mat3(texMinTransfData);
	const Mat3 texMagTransf = Mat3(layerShearTransfData) * Mat3(texMagTransfData);

	// Surface for the reference image.
	tcu::Surface refImage(viewportWidth, viewportHeight);

	{
		const struct Render
		{
			const Rect	region;
			int			textureNdx;
			const Mat3	texTransform;
			Render (const Rect& r, int tN, const Mat3& tT) : region(r), textureNdx(tN), texTransform(tT) {}
		} renders[] =
		{
			Render(Rect(0,				0,				leftWidth,	bottomHeight),	0, texMinTransf),
			Render(Rect(leftWidth,		0,				rightWidth,	bottomHeight),	0, texMagTransf),
			Render(Rect(0,				bottomHeight,	leftWidth,	topHeight),		1, texMinTransf),
			Render(Rect(leftWidth,		bottomHeight,	rightWidth,	topHeight),		1, texMagTransf)
		};

		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
		{
			const Render&	rend				= renders[renderNdx];
			const float		lod					= calculateLod(rend.texTransform, rend.region.size().asFloat(), rend.textureNdx);
			const bool		useSafeTexCoords	= isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
			const Grid		grid				(GRID_SIZE_2D_ARRAY, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
												 TexTypeCoordParams<TEXTURETYPE_2D_ARRAY>(rend.texTransform), useSafeTexCoords);

			glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
			renderCell				(rend.textureNdx, lod, grid);
			computeReferenceCell	(rend.textureNdx, lod, grid, refImage, rend.region);
		}
	}

	// Read back rendered results.
	tcu::Surface resImage(viewportWidth, viewportHeight);
	glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());

	glUseProgram(0);

	// Compare and log.
	{
		const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);

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

	return STOP;
}

void Vertex2DArrayTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
{
	const deUint32 programID = m_program->getProgram();

	// SETUP ATTRIBUTES.

	{
		const int positionLoc = glGetAttribLocation(programID, "a_position");
		if (positionLoc != -1)
		{
			glEnableVertexAttribArray(positionLoc);
			glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
		}
	}

	{
		const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
		if (texCoordLoc != -1)
		{
			glEnableVertexAttribArray(texCoordLoc);
			glVertexAttribPointer(texCoordLoc, 3, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
		}
	}

	// SETUP UNIFORMS.

	{
		const int lodLoc = glGetUniformLocation(programID, "u_lod");
		if (lodLoc != -1)
			glUniform1f(lodLoc, lod);
	}

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D_ARRAY, m_textures[textureNdx]->getGLTexture());
	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S,		m_wrapS);
	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T,		m_wrapT);
	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER,	m_minFilter);
	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER,	m_magFilter);

	{
		const int texLoc = glGetUniformLocation(programID, "u_texture");
		if (texLoc != -1)
			glUniform1i(texLoc, 0);
	}
}

// Renders one sub-image with given parameters.
void Vertex2DArrayTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
{
	setupShaderInputs(textureNdx, lod, grid);
	glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
}

// Computes reference for one sub-image with given parameters.
void Vertex2DArrayTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
{
	computeReference(m_textures[textureNdx]->getRefTexture(), lod, glu::mapGLSampler(m_wrapS, m_wrapT, m_minFilter, m_magFilter), grid, dst, dstRegion);
}

class Vertex3DTextureCase : public TestCase
{
public:
								Vertex3DTextureCase		(Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 wrapR);
								~Vertex3DTextureCase	(void);

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

private:
	typedef PosTexCoordQuadGrid<TEXTURETYPE_3D> Grid;

								Vertex3DTextureCase		(const Vertex3DTextureCase& other);
	Vertex3DTextureCase&		operator=				(const Vertex3DTextureCase& other);

	float						calculateLod			(const Mat3& transf, const Vec2& dstSize, int textureNdx) const;
	void						setupShaderInputs		(int textureNdx, float lod, const Grid& grid) const;
	void						renderCell				(int textureNdx, float lod, const Grid& grid) const;
	void						computeReferenceCell	(int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const;

	const deUint32				m_minFilter;
	const deUint32				m_magFilter;
	const deUint32				m_wrapS;
	const deUint32				m_wrapT;
	const deUint32				m_wrapR;

	const glu::ShaderProgram*	m_program;
	glu::Texture3D*				m_textures[2];	// 2 textures, a gradient texture and a grid texture.
};

Vertex3DTextureCase::Vertex3DTextureCase (Context& testCtx, const char* name, const char* desc, deUint32 minFilter, deUint32 magFilter, deUint32 wrapS, deUint32 wrapT, deUint32 wrapR)
	: TestCase				(testCtx, tcu::NODETYPE_SELF_VALIDATE, name, desc)
	, m_minFilter			(minFilter)
	, m_magFilter			(magFilter)
	, m_wrapS				(wrapS)
	, m_wrapT				(wrapT)
	, m_wrapR				(wrapR)
	, m_program				(DE_NULL)
{
	m_textures[0] = DE_NULL;
	m_textures[1] = DE_NULL;
}

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

void Vertex3DTextureCase::init (void)
{
	const char* const vertexShaderSource =
		"#version 300 es\n"
		"in highp vec2 a_position;\n"
		"in highp vec3 a_texCoord;\n"
		"uniform highp sampler3D u_texture;\n"
		"uniform highp float u_lod;\n"
		"out mediump vec4 v_color;\n"
		"\n"
		"void main()\n"
		"{\n"
		"	gl_Position = vec4(a_position, 0.0, 1.0);\n"
		"	v_color = textureLod(u_texture, a_texCoord, u_lod);\n"
		"}\n";

	const char* const fragmentShaderSource =
		"#version 300 es\n"
		"layout(location = 0) out mediump vec4 dEQP_FragColor;\n"
		"in mediump vec4 v_color;\n"
		"\n"
		"void main()\n"
		"{\n"
		"	dEQP_FragColor = v_color;\n"
		"}\n";

	if (m_context.getRenderTarget().getNumSamples() != 0)
		throw tcu::NotSupportedError("MSAA config not supported by this test");

	// Create shader.

	DE_ASSERT(!m_program);
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vertexShaderSource, fragmentShaderSource));

	if(!m_program->isOk())
	{
		m_testCtx.getLog() << *m_program;

		GLint maxVertexTextures;
		glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextures);

		if (maxVertexTextures < 1)
			throw tcu::NotSupportedError("Vertex texture image units not supported", "", __FILE__, __LINE__);
		else
			TCU_FAIL("Failed to compile shader");
	}

	// Make the textures.

	try
	{
		const int texWidth	= WIDTH_3D;
		const int texHeight	= HEIGHT_3D;
		const int texDepth	= DEPTH_3D;

		for (int i = 0; i < 2; i++)
		{
			DE_ASSERT(!m_textures[i]);
			m_textures[i] = new glu::Texture3D(m_context.getRenderContext(), GL_RGB, GL_UNSIGNED_BYTE, texWidth, texHeight, texDepth);
		}

		const int						numLevels	= deLog2Floor32(de::max(de::max(texWidth, texHeight), texDepth)) + 1;
		const tcu::TextureFormatInfo	fmtInfo		= tcu::getTextureFormatInfo(m_textures[0]->getRefTexture().getFormat());
		const Vec4						cBias		= fmtInfo.valueMin;
		const Vec4						cScale		= fmtInfo.valueMax-fmtInfo.valueMin;

		// Fill first with gradient texture.
		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
		{
			const Vec4 gMin = Vec4(-0.5f, -0.5f, -0.5f, 2.0f)*cScale + cBias;
			const Vec4 gMax = Vec4( 1.0f,  1.0f,  1.0f, 0.0f)*cScale + cBias;

			m_textures[0]->getRefTexture().allocLevel(levelNdx);
			tcu::fillWithComponentGradients(m_textures[0]->getRefTexture().getLevel(levelNdx), gMin, gMax);
		}

		// Fill second with grid texture.
		for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
		{
			const deUint32 step		= 0x00ffffff / numLevels;
			const deUint32 rgb		= step*levelNdx;
			const deUint32 colorA	= 0xff000000 | rgb;
			const deUint32 colorB	= 0xff000000 | ~rgb;

			m_textures[1]->getRefTexture().allocLevel(levelNdx);
			tcu::fillWithGrid(m_textures[1]->getRefTexture().getLevel(levelNdx), 4, toVec4(tcu::RGBA(colorA))*cScale + cBias, toVec4(tcu::RGBA(colorB))*cScale + cBias);
		}

		// Upload.
		for (int i = 0; i < 2; i++)
			m_textures[i]->upload();
	}
	catch (const std::exception&)
	{
		// Clean up to save memory.
		Vertex3DTextureCase::deinit();
		throw;
	}
}

void Vertex3DTextureCase::deinit (void)
{
	for (int i = 0; i < 2; i++)
	{
		delete m_textures[i];
		m_textures[i] = DE_NULL;
	}

	delete m_program;
	m_program = DE_NULL;
}

float Vertex3DTextureCase::calculateLod (const Mat3& transf, const Vec2& dstSize, int textureNdx) const
{
	const tcu::Texture3D&	refTexture	= m_textures[textureNdx]->getRefTexture();
	const int				srcWidth	= refTexture.getWidth();
	const int				srcHeight	= refTexture.getHeight();
	const int				srcDepth	= refTexture.getDepth();

	// Calculate transformed coordinates of three screen corners.
	const Vec3				trans00		= transf * Vec3(0.0f, 0.0f, 1.0f);
	const Vec3				trans01		= transf * Vec3(0.0f, 1.0f, 1.0f);
	const Vec3				trans10		= transf * Vec3(1.0f, 0.0f, 1.0f);

	// Derivates.
	const float dudx = (trans10.x() - trans00.x()) * (float)srcWidth / dstSize.x();
	const float dudy = (trans01.x() - trans00.x()) * (float)srcWidth / dstSize.y();
	const float dvdx = (trans10.y() - trans00.y()) * (float)srcHeight / dstSize.x();
	const float dvdy = (trans01.y() - trans00.y()) * (float)srcHeight / dstSize.y();
	const float dwdx = (trans10.z() - trans00.z()) * (float)srcDepth / dstSize.x();
	const float dwdy = (trans01.z() - trans00.z()) * (float)srcDepth / dstSize.y();

	return deFloatLog2(deFloatSqrt(de::max(dudx*dudx + dvdx*dvdx + dwdx*dwdx, dudy*dudy + dvdy*dvdy + dwdy*dwdy)));
}

Vertex3DTextureCase::IterateResult Vertex3DTextureCase::iterate (void)
{
	const int	viewportWidth		= deMin32(m_context.getRenderTarget().getWidth(), MAX_3D_RENDER_WIDTH);
	const int	viewportHeight		= deMin32(m_context.getRenderTarget().getHeight(), MAX_3D_RENDER_HEIGHT);

	const int	viewportXOffsetMax	= m_context.getRenderTarget().getWidth() - viewportWidth;
	const int	viewportYOffsetMax	= m_context.getRenderTarget().getHeight() - viewportHeight;

	de::Random	rnd					(deStringHash(getName()));

	const int	viewportXOffset		= rnd.getInt(0, viewportXOffsetMax);
	const int	viewportYOffset		= rnd.getInt(0, viewportYOffsetMax);

	glUseProgram(m_program->getProgram());

	// Divide viewport into 4 cells.
	const int leftWidth		= viewportWidth / 2;
	const int rightWidth		= viewportWidth - leftWidth;
	const int bottomHeight	= viewportHeight / 2;
	const int topHeight		= viewportHeight - bottomHeight;

	// Clear.
	glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);

	// Shear to get all slices visible.
	static const float depthShearTransfData[] =
	{
		1.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f,
		1.0f, 1.0f, 0.0f
	};

	// Minification and magnification transformations.
	static const float texMinTransfData[] =
	{
		2.2f,  0.0f, -0.3f,
		0.0f,  2.2f, -0.3f,
		0.0f,  0.0f,  1.0f
	};
	static const float texMagTransfData[] =
	{
		0.4f,  0.0f,  0.8f,
		0.0f,  0.4f,  0.8f,
		0.0f,  0.0f,  1.0f
	};

	// Transformation matrices for minification and magnification.
	const Mat3 texMinTransf = Mat3(depthShearTransfData) * Mat3(texMinTransfData);
	const Mat3 texMagTransf = Mat3(depthShearTransfData) * Mat3(texMagTransfData);

	// Surface for the reference image.
	tcu::Surface refImage(viewportWidth, viewportHeight);

	{
		const struct Render
		{
			const Rect	region;
			int			textureNdx;
			const Mat3		texTransform;
			Render (const Rect& r, int tN, const Mat3& tT) : region(r), textureNdx(tN), texTransform(tT) {}
		} renders[] =
		{
			Render(Rect(0,				0,				leftWidth,	bottomHeight),	0, texMinTransf),
			Render(Rect(leftWidth,		0,				rightWidth,	bottomHeight),	0, texMagTransf),
			Render(Rect(0,				bottomHeight,	leftWidth,	topHeight),		1, texMinTransf),
			Render(Rect(leftWidth,		bottomHeight,	rightWidth,	topHeight),		1, texMagTransf)
		};

		for (int renderNdx = 0; renderNdx < DE_LENGTH_OF_ARRAY(renders); renderNdx++)
		{
			const Render&	rend				= renders[renderNdx];
			const float		lod					= calculateLod(rend.texTransform, rend.region.size().asFloat(), rend.textureNdx);
			const bool		useSafeTexCoords	= isLevelNearest(lod > 0.0f ? m_minFilter : m_magFilter);
			const Grid		grid				(GRID_SIZE_3D, rend.region.size(), getTextureSize(*m_textures[rend.textureNdx]),
												 TexTypeCoordParams<TEXTURETYPE_3D>(rend.texTransform), useSafeTexCoords);

			glViewport(viewportXOffset + rend.region.x, viewportYOffset + rend.region.y, rend.region.w, rend.region.h);
			renderCell				(rend.textureNdx, lod, grid);
			computeReferenceCell	(rend.textureNdx, lod, grid, refImage, rend.region);
		}
	}

	// Read back rendered results.
	tcu::Surface resImage(viewportWidth, viewportHeight);
	glu::readPixels(m_context.getRenderContext(), viewportXOffset, viewportYOffset, resImage.getAccess());

	glUseProgram(0);

	// Compare and log.
	{
		const bool isOk = compareImages(m_context.getRenderContext(), m_testCtx.getLog(), refImage, resImage);

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

	return STOP;
}

void Vertex3DTextureCase::setupShaderInputs (int textureNdx, float lod, const Grid& grid) const
{
	const deUint32 programID = m_program->getProgram();

	// SETUP ATTRIBUTES.

	{
		const int positionLoc = glGetAttribLocation(programID, "a_position");
		if (positionLoc != -1)
		{
			glEnableVertexAttribArray(positionLoc);
			glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, grid.getPositionPtr());
		}
	}

	{
		const int texCoordLoc = glGetAttribLocation(programID, "a_texCoord");
		if (texCoordLoc != -1)
		{
			glEnableVertexAttribArray(texCoordLoc);
			glVertexAttribPointer(texCoordLoc, 3, GL_FLOAT, GL_FALSE, 0, grid.getTexCoordPtr());
		}
	}

	// SETUP UNIFORMS.

	{
		const int lodLoc = glGetUniformLocation(programID, "u_lod");
		if (lodLoc != -1)
			glUniform1f(lodLoc, lod);
	}

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_3D, m_textures[textureNdx]->getGLTexture());
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S,		m_wrapS);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T,		m_wrapT);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R,		m_wrapR);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER,	m_minFilter);
	glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER,	m_magFilter);

	{
		const int texLoc = glGetUniformLocation(programID, "u_texture");
		if (texLoc != -1)
			glUniform1i(texLoc, 0);
	}
}

// Renders one sub-image with given parameters.
void Vertex3DTextureCase::renderCell (int textureNdx, float lod, const Grid& grid) const
{
	setupShaderInputs(textureNdx, lod, grid);
	glDrawElements(GL_TRIANGLES, grid.getNumIndices(), GL_UNSIGNED_SHORT, grid.getIndexPtr());
}

// Computes reference for one sub-image with given parameters.
void Vertex3DTextureCase::computeReferenceCell (int textureNdx, float lod, const Grid& grid, tcu::Surface& dst, const Rect& dstRegion) const
{
	computeReference(m_textures[textureNdx]->getRefTexture(), lod, glu::mapGLSampler(m_wrapS, m_wrapT, m_wrapR, m_minFilter, m_magFilter), grid, dst, dstRegion);
}

VertexTextureTests::VertexTextureTests (Context& context)
	: TestCaseGroup(context, "vertex", "Vertex Texture Tests")
{
}

VertexTextureTests::~VertexTextureTests(void)
{
}

void VertexTextureTests::init (void)
{
	// 2D and cube map groups, and their filtering and wrap sub-groups.
	TestCaseGroup* const group2D				= new TestCaseGroup(m_context, "2d",			"2D Vertex Texture Tests");
	TestCaseGroup* const groupCube				= new TestCaseGroup(m_context, "cube",			"Cube Map Vertex Texture Tests");
	TestCaseGroup* const group2DArray			= new TestCaseGroup(m_context, "2d_array",		"2D Array Vertex Texture Tests");
	TestCaseGroup* const group3D				= new TestCaseGroup(m_context, "3d",			"3D Vertex Texture Tests");
	TestCaseGroup* const filteringGroup2D		= new TestCaseGroup(m_context, "filtering",		"2D Vertex Texture Filtering Tests");
	TestCaseGroup* const wrapGroup2D			= new TestCaseGroup(m_context, "wrap",			"2D Vertex Texture Wrap Tests");
	TestCaseGroup* const filteringGroupCube		= new TestCaseGroup(m_context, "filtering",		"Cube Map Vertex Texture Filtering Tests");
	TestCaseGroup* const wrapGroupCube			= new TestCaseGroup(m_context, "wrap",			"Cube Map Vertex Texture Wrap Tests");
	TestCaseGroup* const filteringGroup2DArray	= new TestCaseGroup(m_context, "filtering",		"2D Array Vertex Texture Filtering Tests");
	TestCaseGroup* const wrapGroup2DArray		= new TestCaseGroup(m_context, "wrap",			"2D Array Vertex Texture Wrap Tests");
	TestCaseGroup* const filteringGroup3D		= new TestCaseGroup(m_context, "filtering",		"3D Vertex Texture Filtering Tests");
	TestCaseGroup* const wrapGroup3D			= new TestCaseGroup(m_context, "wrap",			"3D Vertex Texture Wrap Tests");

	group2D->addChild(filteringGroup2D);
	group2D->addChild(wrapGroup2D);
	groupCube->addChild(filteringGroupCube);
	groupCube->addChild(wrapGroupCube);
	group2DArray->addChild(filteringGroup2DArray);
	group2DArray->addChild(wrapGroup2DArray);
	group3D->addChild(filteringGroup3D);
	group3D->addChild(wrapGroup3D);

	addChild(group2D);
	addChild(groupCube);
	addChild(group2DArray);
	addChild(group3D);

	static const struct
	{
		const char*		name;
		GLenum			mode;
	} wrapModes[] =
	{
		{ "clamp",		GL_CLAMP_TO_EDGE	},
		{ "repeat",		GL_REPEAT			},
		{ "mirror",		GL_MIRRORED_REPEAT	}
	};

	static const struct
	{
		const char*		name;
		GLenum			mode;
	} minFilterModes[] =
	{
		{ "nearest",				GL_NEAREST					},
		{ "linear",					GL_LINEAR					},
		{ "nearest_mipmap_nearest",	GL_NEAREST_MIPMAP_NEAREST	},
		{ "linear_mipmap_nearest",	GL_LINEAR_MIPMAP_NEAREST	},
		{ "nearest_mipmap_linear",	GL_NEAREST_MIPMAP_LINEAR	},
		{ "linear_mipmap_linear",	GL_LINEAR_MIPMAP_LINEAR		}
	};

	static const struct
	{
		const char*		name;
		GLenum			mode;
	} magFilterModes[] =
	{
		{ "nearest",	GL_NEAREST	},
		{ "linear",		GL_LINEAR	}
	};

#define FOR_EACH(ITERATOR, ARRAY, BODY)	\
	for (int ITERATOR = 0; ITERATOR < DE_LENGTH_OF_ARRAY(ARRAY); ITERATOR++)	\
		BODY

	// 2D cases.

	FOR_EACH(minFilter,		minFilterModes,
	FOR_EACH(magFilter,		magFilterModes,
	FOR_EACH(wrapMode,		wrapModes,
		{
			const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;

			filteringGroup2D->addChild(new Vertex2DTextureCase(m_context,
															   name.c_str(), "",
															   minFilterModes[minFilter].mode,
															   magFilterModes[magFilter].mode,
															   wrapModes[wrapMode].mode,
															   wrapModes[wrapMode].mode));
		})));

	FOR_EACH(wrapSMode,		wrapModes,
	FOR_EACH(wrapTMode,		wrapModes,
		{
			const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name;

			wrapGroup2D->addChild(new Vertex2DTextureCase(m_context,
														  name.c_str(), "",
														  GL_LINEAR_MIPMAP_LINEAR,
														  GL_LINEAR,
														  wrapModes[wrapSMode].mode,
														  wrapModes[wrapTMode].mode));
		}));

	// Cube map cases.

	FOR_EACH(minFilter,		minFilterModes,
	FOR_EACH(magFilter,		magFilterModes,
	FOR_EACH(wrapMode,		wrapModes,
		{
			const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;

			filteringGroupCube->addChild(new VertexCubeTextureCase(m_context,
																   name.c_str(), "",
																   minFilterModes[minFilter].mode,
																   magFilterModes[magFilter].mode,
																   wrapModes[wrapMode].mode,
																   wrapModes[wrapMode].mode));
		})));

	FOR_EACH(wrapSMode,		wrapModes,
	FOR_EACH(wrapTMode,		wrapModes,
		{
			const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name;

			wrapGroupCube->addChild(new VertexCubeTextureCase(m_context,
															  name.c_str(), "",
															  GL_LINEAR_MIPMAP_LINEAR,
															  GL_LINEAR,
															  wrapModes[wrapSMode].mode,
															  wrapModes[wrapTMode].mode));
		}));

	// 2D array cases.

	FOR_EACH(minFilter,		minFilterModes,
	FOR_EACH(magFilter,		magFilterModes,
	FOR_EACH(wrapMode,		wrapModes,
		{
			const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;

			filteringGroup2DArray->addChild(new Vertex2DArrayTextureCase(m_context,
																		 name.c_str(), "",
																		 minFilterModes[minFilter].mode,
																		 magFilterModes[magFilter].mode,
																		 wrapModes[wrapMode].mode,
																		 wrapModes[wrapMode].mode));
		})));

	FOR_EACH(wrapSMode,		wrapModes,
	FOR_EACH(wrapTMode,		wrapModes,
		{
			const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name;

			wrapGroup2DArray->addChild(new Vertex2DArrayTextureCase(m_context,
																	name.c_str(), "",
																	GL_LINEAR_MIPMAP_LINEAR,
																	GL_LINEAR,
																	wrapModes[wrapSMode].mode,
																	wrapModes[wrapTMode].mode));
		}));

	// 3D cases.

	FOR_EACH(minFilter,		minFilterModes,
	FOR_EACH(magFilter,		magFilterModes,
	FOR_EACH(wrapMode,		wrapModes,
		{
			const string name = string("") + minFilterModes[minFilter].name + "_" + magFilterModes[magFilter].name + "_" + wrapModes[wrapMode].name;

			filteringGroup3D->addChild(new Vertex3DTextureCase(m_context,
															   name.c_str(), "",
															   minFilterModes[minFilter].mode,
															   magFilterModes[magFilter].mode,
															   wrapModes[wrapMode].mode,
															   wrapModes[wrapMode].mode,
															   wrapModes[wrapMode].mode));
		})));

	FOR_EACH(wrapSMode,		wrapModes,
	FOR_EACH(wrapTMode,		wrapModes,
	FOR_EACH(wrapRMode,		wrapModes,
		{
			const string name = string("") + wrapModes[wrapSMode].name + "_" + wrapModes[wrapTMode].name + "_" + wrapModes[wrapRMode].name;

			wrapGroup3D->addChild(new Vertex3DTextureCase(m_context,
														  name.c_str(), "",
														  GL_LINEAR_MIPMAP_LINEAR,
														  GL_LINEAR,
														  wrapModes[wrapSMode].mode,
														  wrapModes[wrapTMode].mode,
														  wrapModes[wrapRMode].mode));
		})));
}

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