/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 2.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 Texture specification tests.
 *
 * \todo [pyry] Following tests are missing:
 *  - Specify mipmap incomplete texture, use without mipmaps, re-specify
 *    as complete and render.
 *  - Randomly re-specify levels to eventually reach mipmap-complete texture.
 *//*--------------------------------------------------------------------*/

#include "es2fTextureSpecificationTests.hpp"
#include "tcuTestLog.hpp"
#include "tcuImageCompare.hpp"
#include "gluTextureUtil.hpp"
#include "sglrContextUtil.hpp"
#include "sglrContextWrapper.hpp"
#include "sglrGLContext.hpp"
#include "sglrReferenceContext.hpp"
#include "glsTextureTestUtil.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuFormatUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"

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

namespace deqp
{
namespace gles2
{
namespace Functional
{

using std::string;
using std::vector;
using std::pair;
using tcu::TestLog;
using tcu::Vec4;
using tcu::IVec4;
using tcu::UVec4;

tcu::TextureFormat mapGLUnsizedInternalFormat (deUint32 internalFormat)
{
	using tcu::TextureFormat;
	switch (internalFormat)
	{
		case GL_ALPHA:				return TextureFormat(TextureFormat::A,		TextureFormat::UNORM_INT8);
		case GL_LUMINANCE:			return TextureFormat(TextureFormat::L,		TextureFormat::UNORM_INT8);
		case GL_LUMINANCE_ALPHA:	return TextureFormat(TextureFormat::LA,		TextureFormat::UNORM_INT8);
		case GL_RGB:				return TextureFormat(TextureFormat::RGB,	TextureFormat::UNORM_INT8);
		case GL_RGBA:				return TextureFormat(TextureFormat::RGBA,	TextureFormat::UNORM_INT8);
		default:
			throw tcu::InternalError(string("Can't map GL unsized internal format (") + tcu::toHex(internalFormat).toString() + ") to texture format");
	}
}

template <int Size>
static tcu::Vector<float, Size> randomVector (de::Random& rnd, const tcu::Vector<float, Size>& minVal = tcu::Vector<float, Size>(0.0f), const tcu::Vector<float, Size>& maxVal = tcu::Vector<float, Size>(1.0f))
{
	tcu::Vector<float, Size> res;
	for (int ndx = 0; ndx < Size; ndx++)
		res[ndx] = rnd.getFloat(minVal[ndx], maxVal[ndx]);
	return res;
}

static tcu::IVec4 getPixelFormatCompareDepth (const tcu::PixelFormat& pixelFormat, tcu::TextureFormat textureFormat)
{
	switch (textureFormat.order)
	{
		case tcu::TextureFormat::L:
		case tcu::TextureFormat::LA:
			return tcu::IVec4(pixelFormat.redBits, pixelFormat.redBits, pixelFormat.redBits, pixelFormat.alphaBits);
		default:
			return tcu::IVec4(pixelFormat.redBits, pixelFormat.greenBits, pixelFormat.blueBits, pixelFormat.alphaBits);
	}
}

static tcu::UVec4 computeCompareThreshold (const tcu::PixelFormat& pixelFormat, tcu::TextureFormat textureFormat)
{
	const IVec4		texFormatBits		= tcu::getTextureFormatBitDepth(textureFormat);
	const IVec4		pixelFormatBits		= getPixelFormatCompareDepth(pixelFormat, textureFormat);
	const IVec4		accurateFmtBits		= min(pixelFormatBits, texFormatBits);
	const IVec4		compareBits			= select(accurateFmtBits, IVec4(8), greaterThan(accurateFmtBits, IVec4(0))) - 1;

	return (IVec4(1) << (8-compareBits)).asUint();
}

class GradientShader : public sglr::ShaderProgram
{
public:
	GradientShader (void)
		: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexSource("attribute highp vec4 a_position;\n"
												"attribute mediump vec2 a_coord;\n"
												"varying mediump vec2 v_coord;\n"
												"void main (void)\n"
												"{\n"
												"	gl_Position = a_position;\n"
												"	v_coord = a_coord;\n"
												"}\n")
					<< sglr::pdec::FragmentSource("varying mediump vec2 v_coord;\n"
												  "void main (void)\n"
												  "{\n"
												  "	mediump float x = v_coord.x;\n"
												  "	mediump float y = v_coord.y;\n"
												  "	mediump float f0 = (x + y) * 0.5;\n"
												  "	mediump float f1 = 0.5 + (x - y) * 0.5;\n"
												  "	gl_FragColor = vec4(f0, f1, 1.0-f0, 1.0-f1);\n"
												  "}\n"))
	{
	}

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

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

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

			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, tcu::Vec4(f0, f1, 1.0f-f0, 1.0f-f1));
		}
	}
};

class Tex2DShader : public sglr::ShaderProgram
{
public:
	Tex2DShader (void)
		: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::Uniform("u_sampler0", glu::TYPE_SAMPLER_2D)
					<< sglr::pdec::VertexSource("attribute highp vec4 a_position;\n"
												"attribute mediump vec2 a_coord;\n"
												"varying mediump vec2 v_coord;\n"
												"void main (void)\n"
												"{\n"
												"	gl_Position = a_position;\n"
												"	v_coord = a_coord;\n"
												"}\n")
					<< sglr::pdec::FragmentSource("uniform sampler2D u_sampler0;\n"
												  "varying mediump vec2 v_coord;\n"
												  "void main (void)\n"
												  "{\n"
												  "	gl_FragColor = texture2D(u_sampler0, v_coord);\n"
												  "}\n"))
	{
	}

	void setUniforms (sglr::Context& ctx, deUint32 program) const
	{
		ctx.useProgram(program);
		ctx.uniform1i(ctx.getUniformLocation(program, "u_sampler0"), 0);
	}

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

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

	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
	{
		tcu::Vec2 texCoords[4];
		tcu::Vec4 colors[4];

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

			// Sample
			m_uniforms[0].sampler.tex2D->sample4(colors, texCoords);

			// Write out
			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
				rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, colors[fragNdx]);
		}
	}
};

static const char* s_cubeSwizzles[] =
{
	"vec3(-1, -y, +x)",
	"vec3(+1, -y, -x)",
	"vec3(+x, -1, -y)",
	"vec3(+x, +1, +y)",
	"vec3(-x, -y, -1)",
	"vec3(+x, -y, +1)"
};

class TexCubeShader : public sglr::ShaderProgram
{
public:
	TexCubeShader (tcu::CubeFace face)
		: ShaderProgram(sglr::pdec::ShaderProgramDeclaration()
					<< sglr::pdec::VertexAttribute("a_position", rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexAttribute("a_coord", rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT)
					<< sglr::pdec::Uniform("u_sampler0", glu::TYPE_SAMPLER_CUBE)
					<< sglr::pdec::VertexSource("attribute highp vec4 a_position;\n"
												"attribute mediump vec2 a_coord;\n"
												"varying mediump vec2 v_coord;\n"
												"void main (void)\n"
												"{\n"
												"	gl_Position = a_position;\n"
												"	v_coord = a_coord;\n"
												"}\n")
					<< sglr::pdec::FragmentSource(string("") +
												  "uniform samplerCube u_sampler0;\n"
												  "varying mediump vec2 v_coord;\n"
												  "void main (void)\n"
												  "{\n"
												  "	mediump float x = v_coord.x*2.0 - 1.0;\n"
												  "	mediump float y = v_coord.y*2.0 - 1.0;\n"
												  "	gl_FragColor = textureCube(u_sampler0, " + s_cubeSwizzles[face] + ");\n"
												  "}\n"))
		, m_face(face)
	{
	}

	void setUniforms (sglr::Context& ctx, deUint32 program) const
	{
		ctx.useProgram(program);
		ctx.uniform1i(ctx.getUniformLocation(program, "u_sampler0"), 0);
	}

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

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

	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
	{
		tcu::Vec3 texCoords[4];
		tcu::Vec4 colors[4];

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

				// Swizzle tex coords
				switch (m_face)
				{
					case tcu::CUBEFACE_NEGATIVE_X:	texCoords[fragNdx] = tcu::Vec3(-1.0f,    -y,    +x);		break;
					case tcu::CUBEFACE_POSITIVE_X:	texCoords[fragNdx] = tcu::Vec3(+1.0f,    -y,    -x);		break;
					case tcu::CUBEFACE_NEGATIVE_Y:	texCoords[fragNdx] = tcu::Vec3(   +x, -1.0f,    -y);		break;
					case tcu::CUBEFACE_POSITIVE_Y:	texCoords[fragNdx] = tcu::Vec3(   +x, +1.0f,    +y);		break;
					case tcu::CUBEFACE_NEGATIVE_Z:	texCoords[fragNdx] = tcu::Vec3(   -x,    -y, -1.0f);		break;
					case tcu::CUBEFACE_POSITIVE_Z:	texCoords[fragNdx] = tcu::Vec3(   +x,    -y, +1.0f);		break;
					default:
						DE_ASSERT(false);
				}
			}

			// Sample
			m_uniforms[0].sampler.texCube->sample4(colors, texCoords);

			// Write out
			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
				rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, colors[fragNdx]);
		}
	}
private:
	tcu::CubeFace m_face;
};

enum TextureType
{
	TEXTURETYPE_2D = 0,
	TEXTURETYPE_CUBE,

	TEXTURETYPE_LAST
};

enum Flags
{
	MIPMAPS		= (1<<0)
};

static const deUint32 s_cubeMapFaces[] =
{
	GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
	GL_TEXTURE_CUBE_MAP_POSITIVE_X,
	GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
	GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
	GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
	GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
};

class TextureSpecCase : public TestCase, public sglr::ContextWrapper
{
public:
								TextureSpecCase		(Context& context, const char* name, const char* desc, const TextureType type, const tcu::TextureFormat format, const deUint32 flags, int width, int height);
								~TextureSpecCase	(void);

	IterateResult				iterate				(void);

protected:
	virtual void				createTexture		(void) = DE_NULL;

	const TextureType			m_texType;
	const tcu::TextureFormat	m_texFormat;
	const deUint32				m_flags;
	const int					m_width;
	const int					m_height;

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

	void						verifyTex2D			(sglr::GLContext& gles2Context, sglr::ReferenceContext& refContext);
	void						verifyTexCube		(sglr::GLContext& gles2Context, sglr::ReferenceContext& refContext);

	void						renderTex2D			(tcu::Surface& dst, int width, int height);
	void						renderTexCube		(tcu::Surface& dst, int width, int height, tcu::CubeFace face);

	void						readPixels			(tcu::Surface& dst, int x, int y, int width, int height);

	// \todo [2012-03-27 pyry] Renderer should be extended to allow custom attributes, that would clean up this cubemap mess.
	Tex2DShader					m_tex2DShader;
	TexCubeShader				m_texCubeNegXShader;
	TexCubeShader				m_texCubePosXShader;
	TexCubeShader				m_texCubeNegYShader;
	TexCubeShader				m_texCubePosYShader;
	TexCubeShader				m_texCubeNegZShader;
	TexCubeShader				m_texCubePosZShader;
};

TextureSpecCase::TextureSpecCase (Context& context, const char* name, const char* desc, const TextureType type, const tcu::TextureFormat format, const deUint32 flags, int width, int height)
	: TestCase				(context, name, desc)
	, m_texType				(type)
	, m_texFormat			(format)
	, m_flags				(flags)
	, m_width				(width)
	, m_height				(height)
	, m_texCubeNegXShader	(tcu::CUBEFACE_NEGATIVE_X)
	, m_texCubePosXShader	(tcu::CUBEFACE_POSITIVE_X)
	, m_texCubeNegYShader	(tcu::CUBEFACE_NEGATIVE_Y)
	, m_texCubePosYShader	(tcu::CUBEFACE_POSITIVE_Y)
	, m_texCubeNegZShader	(tcu::CUBEFACE_NEGATIVE_Z)
	, m_texCubePosZShader	(tcu::CUBEFACE_POSITIVE_Z)
{
}

TextureSpecCase::~TextureSpecCase (void)
{
}

TextureSpecCase::IterateResult TextureSpecCase::iterate (void)
{
	glu::RenderContext&			renderCtx				= TestCase::m_context.getRenderContext();
	const tcu::RenderTarget&	renderTarget			= renderCtx.getRenderTarget();
	tcu::TestLog&				log						= m_testCtx.getLog();

	DE_ASSERT(m_width <= 256 && m_height <= 256);
	if (renderTarget.getWidth() < m_width || renderTarget.getHeight() < m_height)
		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);

	// Context size, and viewport for GLES2
	de::Random		rnd			(deStringHash(getName()));
	int				width		= deMin32(renderTarget.getWidth(),	256);
	int				height		= deMin32(renderTarget.getHeight(),	256);
	int				x			= rnd.getInt(0, renderTarget.getWidth()		- width);
	int				y			= rnd.getInt(0, renderTarget.getHeight()	- height);

	// Contexts.
	sglr::GLContext					gles2Context	(renderCtx, log, sglr::GLCONTEXT_LOG_CALLS, tcu::IVec4(x, y, width, height));
	sglr::ReferenceContextBuffers	refBuffers		(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), 0 /* depth */, 0 /* stencil */, width, height);
	sglr::ReferenceContext			refContext		(sglr::ReferenceContextLimits(renderCtx), refBuffers.getColorbuffer(), refBuffers.getDepthbuffer(), refBuffers.getStencilbuffer());

	// Clear color buffer.
	for (int ndx = 0; ndx < 2; ndx++)
	{
		setContext(ndx ? (sglr::Context*)&refContext : (sglr::Context*)&gles2Context);
		glClearColor(0.125f, 0.25f, 0.5f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
	}

	// Construct texture using both GLES2 and reference contexts.
	for (int ndx = 0; ndx < 2; ndx++)
	{
		setContext(ndx ? (sglr::Context*)&refContext : (sglr::Context*)&gles2Context);
		createTexture();
		TCU_CHECK(glGetError() == GL_NO_ERROR);
	}

	// Setup texture filtering state.
	for (int ndx = 0; ndx < 2; ndx++)
	{
		setContext(ndx ? (sglr::Context*)&refContext : (sglr::Context*)&gles2Context);

		deUint32 texTarget = m_texType == TEXTURETYPE_2D ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP;
		glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER,	(m_flags & MIPMAPS) ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
		glTexParameteri(texTarget, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
	}

	// Initialize case result to pass.
	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	// Disable logging.
	gles2Context.enableLogging(0);

	// Verify results.
	switch (m_texType)
	{
		case TEXTURETYPE_2D:	verifyTex2D		(gles2Context, refContext);	break;
		case TEXTURETYPE_CUBE:	verifyTexCube	(gles2Context, refContext);	break;
		default:
			DE_ASSERT(false);
	}

	return STOP;
}

void TextureSpecCase::verifyTex2D (sglr::GLContext& gles2Context, sglr::ReferenceContext& refContext)
{
	int numLevels = (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;

	DE_ASSERT(m_texType == TEXTURETYPE_2D);

	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
	{
		int				levelW		= de::max(1, m_width >> levelNdx);
		int				levelH		= de::max(1, m_height >> levelNdx);
		tcu::Surface	reference;
		tcu::Surface	result;

		if (levelW <= 2 || levelH <= 2)
			continue; // Don't bother checking.

		// Render with GLES2
		setContext(&gles2Context);
		renderTex2D(result, levelW, levelH);

		// Render reference.
		setContext(&refContext);
		renderTex2D(reference, levelW, levelH);

		{
			tcu::UVec4	threshold	= computeCompareThreshold(m_context.getRenderTarget().getPixelFormat(), m_texFormat);
			bool		isOk		= tcu::intThresholdCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference.getAccess(), result.getAccess(), threshold,
															   levelNdx == 0 ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);

			if (!isOk)
			{
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
				break;
			}
		}
	}
}

void TextureSpecCase::verifyTexCube (sglr::GLContext& gles2Context, sglr::ReferenceContext& refContext)
{
	int numLevels = (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;

	DE_ASSERT(m_texType == TEXTURETYPE_CUBE);

	for (int levelNdx = 0; levelNdx < numLevels; levelNdx++)
	{
		int		levelW	= de::max(1, m_width >> levelNdx);
		int		levelH	= de::max(1, m_height >> levelNdx);
		bool	isOk	= true;

		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
		{
			tcu::Surface	reference;
			tcu::Surface	result;

			if (levelW <= 2 || levelH <= 2)
				continue; // Don't bother checking.

			// Render with GLES2
			setContext(&gles2Context);
			renderTexCube(result, levelW, levelH, (tcu::CubeFace)face);

			// Render reference.
			setContext(&refContext);
			renderTexCube(reference, levelW, levelH, (tcu::CubeFace)face);

			const float	threshold	= 0.02f;
			isOk = tcu::fuzzyCompare(m_testCtx.getLog(), "Result", (string("Image comparison result: ") + de::toString((tcu::CubeFace)face)).c_str(), reference, result, threshold,
									 levelNdx == 0 ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);

			if (!isOk)
			{
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
				break;
			}
		}

		if (!isOk)
			break;
	}
}

void TextureSpecCase::renderTex2D (tcu::Surface& dst, int width, int height)
{
	int			targetW		= getWidth();
	int			targetH		= getHeight();

	float		w			= (float)width	/ (float)targetW;
	float		h			= (float)height	/ (float)targetH;

	deUint32	shaderID	= getCurrentContext()->createProgram(&m_tex2DShader);

	m_tex2DShader.setUniforms(*getCurrentContext(), shaderID);
	sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(-1.0f + w*2.0f, -1.0f + h*2.0f, 0.0f));

	// Read pixels back.
	readPixels(dst, 0, 0, width, height);
}

void TextureSpecCase::renderTexCube (tcu::Surface& dst, int width, int height, tcu::CubeFace face)
{
	int		targetW		= getWidth();
	int		targetH		= getHeight();

	float	w			= (float)width	/ (float)targetW;
	float	h			= (float)height	/ (float)targetH;

	TexCubeShader* shaders[] =
	{
		&m_texCubeNegXShader,
		&m_texCubePosXShader,
		&m_texCubeNegYShader,
		&m_texCubePosYShader,
		&m_texCubeNegZShader,
		&m_texCubePosZShader
	};

	deUint32	shaderID	= getCurrentContext()->createProgram(shaders[face]);

	shaders[face]->setUniforms(*getCurrentContext(), shaderID);
	sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(-1.0f + w*2.0f, -1.0f + h*2.0f, 0.0f));

	// Read pixels back.
	readPixels(dst, 0, 0, width, height);
}

void TextureSpecCase::readPixels (tcu::Surface& dst, int x, int y, int width, int height)
{
	dst.setSize(width, height);
	glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, dst.getAccess().getDataPtr());
}

// Basic TexImage2D() with 2D texture usage
class BasicTexImage2DCase : public TextureSpecCase
{
public:
	BasicTexImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32			tex			= 0;
		tcu::TextureLevel	levelData	(fmt);
		de::Random			rnd			(deStringHash(getName()));

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_2D, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);
			Vec4	gMin		= randomVector<4>(rnd);
			Vec4	gMax		= randomVector<4>(rnd);

			levelData.setSize(levelW, levelH);
			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);

			glTexImage2D(GL_TEXTURE_2D, ndx, m_format, levelW, levelH, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
};

// Basic TexImage2D() with cubemap usage
class BasicTexImageCubeCase : public TextureSpecCase
{
public:
	BasicTexImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32			tex			= 0;
		tcu::TextureLevel	levelData	(fmt);
		de::Random			rnd			(deStringHash(getName()));

		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);

			levelData.setSize(levelW, levelH);

			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
			{
				Vec4 gMin = randomVector<4>(rnd);
				Vec4 gMax = randomVector<4>(rnd);

				tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);

				glTexImage2D(s_cubeMapFaces[face], ndx, m_format, levelW, levelH, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
			}
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
};

// Randomized 2D texture specification using TexImage2D
class RandomOrderTexImage2DCase : public TextureSpecCase
{
public:
	RandomOrderTexImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32			tex			= 0;
		tcu::TextureLevel	levelData	(fmt);
		de::Random			rnd			(deStringHash(getName()));

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_2D, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		vector<int>			levels		(numLevels);

		for (int i = 0; i < numLevels; i++)
			levels[i] = i;
		rnd.shuffle(levels.begin(), levels.end());

		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelNdx	= levels[ndx];
			int		levelW		= de::max(1, m_width	>> levelNdx);
			int		levelH		= de::max(1, m_height	>> levelNdx);
			Vec4	gMin		= randomVector<4>(rnd);
			Vec4	gMax		= randomVector<4>(rnd);

			levelData.setSize(levelW, levelH);
			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);

			glTexImage2D(GL_TEXTURE_2D, levelNdx, m_format, levelW, levelH, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
};

// Randomized cubemap texture specification using TexImage2D
class RandomOrderTexImageCubeCase : public TextureSpecCase
{
public:
	RandomOrderTexImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32			tex			= 0;
		tcu::TextureLevel	levelData	(fmt);
		de::Random			rnd			(deStringHash(getName()));

		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		// Level-face pairs.
		vector<pair<int, tcu::CubeFace> >	images	(numLevels*6);

		for (int ndx = 0; ndx < numLevels; ndx++)
			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
				images[ndx*6 + face] = std::make_pair(ndx, (tcu::CubeFace)face);

		rnd.shuffle(images.begin(), images.end());

		for (int ndx = 0; ndx < (int)images.size(); ndx++)
		{
			int				levelNdx	= images[ndx].first;
			tcu::CubeFace	face		= images[ndx].second;
			int				levelW		= de::max(1, m_width >> levelNdx);
			int				levelH		= de::max(1, m_height >> levelNdx);
			Vec4			gMin		= randomVector<4>(rnd);
			Vec4			gMax		= randomVector<4>(rnd);

			levelData.setSize(levelW, levelH);
			tcu::fillWithComponentGradients(levelData.getAccess(), gMin, gMax);

			glTexImage2D(s_cubeMapFaces[face], levelNdx, m_format, levelW, levelH, 0, m_format, m_dataType, levelData.getAccess().getDataPtr());
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
};

static inline int getRowPitch (const tcu::TextureFormat& transferFmt, int rowLen, int alignment)
{
	int basePitch = transferFmt.getPixelSize()*rowLen;
	return alignment*(basePitch/alignment + ((basePitch % alignment) ? 1 : 0));
}

// TexImage2D() unpack alignment case.
class TexImage2DAlignCase : public TextureSpecCase
{
public:
	TexImage2DAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height, int alignment)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
		, m_alignment		(alignment)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32			tex			= 0;
		vector<deUint8>		data;

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_2D, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);

		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);
			Vec4	colorA		(1.0f, 0.0f, 0.0f, 1.0f);
			Vec4	colorB		(0.0f, 1.0f, 0.0f, 1.0f);
			int		rowPitch	= getRowPitch(fmt, levelW, m_alignment);
			int		cellSize	= de::max(1, de::min(levelW >> 2, levelH >> 2));

			data.resize(rowPitch*levelH);
			tcu::fillWithGrid(tcu::PixelBufferAccess(fmt, levelW, levelH, 1, rowPitch, 0, &data[0]), cellSize, colorA, colorB);

			glTexImage2D(GL_TEXTURE_2D, ndx, m_format, levelW, levelH, 0, m_format, m_dataType, &data[0]);
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
	int			m_alignment;
};

// TexImage2D() unpack alignment case.
class TexImageCubeAlignCase : public TextureSpecCase
{
public:
	TexImageCubeAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height, int alignment)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
		, m_alignment		(alignment)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32			tex			= 0;
		vector<deUint8>		data;

		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);

		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);
			int		rowPitch	= getRowPitch(fmt, levelW, m_alignment);
			Vec4	colorA		(1.0f, 0.0f, 0.0f, 1.0f);
			Vec4	colorB		(0.0f, 1.0f, 0.0f, 1.0f);
			int		cellSize	= de::max(1, de::min(levelW >> 2, levelH >> 2));

			data.resize(rowPitch*levelH);
			tcu::fillWithGrid(tcu::PixelBufferAccess(fmt, levelW, levelH, 1, rowPitch, 0, &data[0]), cellSize, colorA, colorB);

			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
				glTexImage2D(s_cubeMapFaces[face], ndx, m_format, levelW, levelH, 0, m_format, m_dataType, &data[0]);
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
	int			m_alignment;
};

// Basic TexSubImage2D() with 2D texture usage
class BasicTexSubImage2DCase : public TextureSpecCase
{
public:
	BasicTexSubImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32			tex			= 0;
		tcu::TextureLevel	data		(fmt);
		de::Random			rnd			(deStringHash(getName()));

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_2D, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		// First specify full texture.
		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);
			Vec4	gMin		= randomVector<4>(rnd);
			Vec4	gMax		= randomVector<4>(rnd);

			data.setSize(levelW, levelH);
			tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);

			glTexImage2D(GL_TEXTURE_2D, ndx, m_format, levelW, levelH, 0, m_format, m_dataType, data.getAccess().getDataPtr());
		}

		// Re-specify parts of each level.
		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);

			int		w			= rnd.getInt(1, levelW);
			int		h			= rnd.getInt(1, levelH);
			int		x			= rnd.getInt(0, levelW-w);
			int		y			= rnd.getInt(0, levelH-h);

			Vec4	colorA		= randomVector<4>(rnd);
			Vec4	colorB		= randomVector<4>(rnd);
			int		cellSize	= rnd.getInt(2, 16);

			data.setSize(w, h);
			tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);

			glTexSubImage2D(GL_TEXTURE_2D, ndx, x, y, w, h, m_format, m_dataType, data.getAccess().getDataPtr());
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
};

// Basic TexSubImage2D() with cubemap usage
class BasicTexSubImageCubeCase : public TextureSpecCase
{
public:
	BasicTexSubImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32			tex			= 0;
		tcu::TextureLevel	data		(fmt);
		de::Random			rnd			(deStringHash(getName()));

		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);

			data.setSize(levelW, levelH);

			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
			{
				Vec4 gMin = randomVector<4>(rnd);
				Vec4 gMax = randomVector<4>(rnd);

				tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);

				glTexImage2D(s_cubeMapFaces[face], ndx, m_format, levelW, levelH, 0, m_format, m_dataType, data.getAccess().getDataPtr());
			}
		}

		// Re-specify parts of each face and level.
		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);

			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
			{
				int		w			= rnd.getInt(1, levelW);
				int		h			= rnd.getInt(1, levelH);
				int		x			= rnd.getInt(0, levelW-w);
				int		y			= rnd.getInt(0, levelH-h);

				Vec4	colorA		= randomVector<4>(rnd);
				Vec4	colorB		= randomVector<4>(rnd);
				int		cellSize	= rnd.getInt(2, 16);

				data.setSize(w, h);
				tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);

				glTexSubImage2D(s_cubeMapFaces[face], ndx, x, y, w, h, m_format, m_dataType, data.getAccess().getDataPtr());
			}
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
};

// TexSubImage2D() to texture initialized with empty data
class TexSubImage2DEmptyTexCase : public TextureSpecCase
{
public:
	TexSubImage2DEmptyTexCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32			tex			= 0;
		tcu::TextureLevel	data		(fmt);
		de::Random			rnd			(deStringHash(getName()));

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_2D, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		// First allocate storage for each level.
		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);

			glTexImage2D(GL_TEXTURE_2D, ndx, m_format, levelW, levelH, 0, m_format, m_dataType, DE_NULL);
		}

		// Specify pixel data to all levels using glTexSubImage2D()
		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);
			Vec4	gMin		= randomVector<4>(rnd);
			Vec4	gMax		= randomVector<4>(rnd);

			data.setSize(levelW, levelH);
			tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);

			glTexSubImage2D(GL_TEXTURE_2D, ndx, 0, 0, levelW, levelH, m_format, m_dataType, data.getAccess().getDataPtr());
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
};

// TexSubImage2D() to empty cubemap texture
class TexSubImageCubeEmptyTexCase : public TextureSpecCase
{
public:
	TexSubImageCubeEmptyTexCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		int					numLevels	= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32			tex			= 0;
		tcu::TextureLevel	data		(fmt);
		de::Random			rnd			(deStringHash(getName()));

		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		// Specify storage for each level.
		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);

			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
				glTexImage2D(s_cubeMapFaces[face], ndx, m_format, levelW, levelH, 0, m_format, m_dataType, DE_NULL);
		}

		// Specify data using glTexSubImage2D()
		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);

			data.setSize(levelW, levelH);

			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
			{
				Vec4 gMin = randomVector<4>(rnd);
				Vec4 gMax = randomVector<4>(rnd);

				tcu::fillWithComponentGradients(data.getAccess(), gMin, gMax);

				glTexSubImage2D(s_cubeMapFaces[face], ndx, 0, 0, levelW, levelH, m_format, m_dataType, data.getAccess().getDataPtr());
			}
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
};

// TexSubImage2D() unpack alignment with 2D texture
class TexSubImage2DAlignCase : public TextureSpecCase
{
public:
	TexSubImage2DAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int width, int height, int subX, int subY, int subW, int subH, int alignment)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), 0 /* Mipmaps are never used */, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
		, m_subX			(subX)
		, m_subY			(subY)
		, m_subW			(subW)
		, m_subH			(subH)
		, m_alignment		(alignment)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		deUint32			tex			= 0;
		vector<deUint8>		data;

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_2D, tex);

		// Specify base level.
		data.resize(fmt.getPixelSize()*m_width*m_height);
		tcu::fillWithComponentGradients(tcu::PixelBufferAccess(fmt, m_width, m_height, 1, &data[0]), Vec4(0.0f), Vec4(1.0f));

		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		glTexImage2D(GL_TEXTURE_2D, 0, m_format, m_width, m_height, 0, m_format, m_dataType, &data[0]);

		// Re-specify subrectangle.
		int rowPitch = getRowPitch(fmt, m_subW, m_alignment);
		data.resize(rowPitch*m_subH);
		tcu::fillWithGrid(tcu::PixelBufferAccess(fmt, m_subW, m_subH, 1, rowPitch, 0, &data[0]), 4, Vec4(1.0f, 0.0f, 0.0f, 1.0f), Vec4(0.0f, 1.0f, 0.0f, 1.0f));

		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);
		glTexSubImage2D(GL_TEXTURE_2D, 0, m_subX, m_subY, m_subW, m_subH, m_format, m_dataType, &data[0]);
	}

	deUint32	m_format;
	deUint32	m_dataType;
	int			m_subX;
	int			m_subY;
	int			m_subW;
	int			m_subH;
	int			m_alignment;
};

// TexSubImage2D() unpack alignment with cubemap texture
class TexSubImageCubeAlignCase : public TextureSpecCase
{
public:
	TexSubImageCubeAlignCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, int width, int height, int subX, int subY, int subW, int subH, int alignment)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), 0 /* Mipmaps are never used */, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
		, m_subX			(subX)
		, m_subY			(subY)
		, m_subW			(subW)
		, m_subH			(subH)
		, m_alignment		(alignment)
	{
	}

protected:
	void createTexture (void)
	{
		tcu::TextureFormat	fmt			= m_texFormat;
		deUint32			tex			= 0;
		vector<deUint8>		data;

		DE_ASSERT(m_width == m_height);

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);

		// Specify base level.
		data.resize(fmt.getPixelSize()*m_width*m_height);
		tcu::fillWithComponentGradients(tcu::PixelBufferAccess(fmt, m_width, m_height, 1, &data[0]), Vec4(0.0f), Vec4(1.0f));

		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
			glTexImage2D(s_cubeMapFaces[face], 0, m_format, m_width, m_height, 0, m_format, m_dataType, &data[0]);

		// Re-specify subrectangle.
		int rowPitch = getRowPitch(fmt, m_subW, m_alignment);
		data.resize(rowPitch*m_subH);
		tcu::fillWithGrid(tcu::PixelBufferAccess(fmt, m_subW, m_subH, 1, rowPitch, 0, &data[0]), 4, Vec4(1.0f, 0.0f, 0.0f, 1.0f), Vec4(0.0f, 1.0f, 0.0f, 1.0f));

		glPixelStorei(GL_UNPACK_ALIGNMENT, m_alignment);
		for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
			glTexSubImage2D(s_cubeMapFaces[face], 0, m_subX, m_subY, m_subW, m_subH, m_format, m_dataType, &data[0]);
	}

	deUint32	m_format;
	deUint32	m_dataType;
	int			m_subX;
	int			m_subY;
	int			m_subW;
	int			m_subH;
	int			m_alignment;
};



// Basic CopyTexImage2D() with 2D texture usage
class BasicCopyTexImage2DCase : public TextureSpecCase
{
public:
	BasicCopyTexImage2DCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, mapGLUnsizedInternalFormat(internalFormat), flags, width, height)
		, m_internalFormat	(internalFormat)
	{
	}

protected:
	void createTexture (void)
	{
		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
		tcu::TextureFormat			fmt				= m_texFormat;
		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
		int							numLevels		= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32					tex				= 0;
		de::Random					rnd				(deStringHash(getName()));
		GradientShader				shader;
		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);

		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);

		// Fill render target with gradient.
		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_2D, tex);

		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);
			int		x			= rnd.getInt(0, getWidth()	- levelW);
			int		y			= rnd.getInt(0, getHeight()	- levelH);

			glCopyTexImage2D(GL_TEXTURE_2D, ndx, m_internalFormat, x, y, levelW, levelH, 0);
		}
	}

	deUint32 m_internalFormat;
};

// Basic CopyTexImage2D() with cubemap usage
class BasicCopyTexImageCubeCase : public TextureSpecCase
{
public:
	BasicCopyTexImageCubeCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, mapGLUnsizedInternalFormat(internalFormat), flags, width, height)
		, m_internalFormat	(internalFormat)
	{
	}

protected:
	void createTexture (void)
	{
		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
		tcu::TextureFormat			fmt				= m_texFormat;
		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
		int							numLevels		= (m_flags & MIPMAPS) ? deLog2Floor32(m_width)+1 : 1;
		deUint32					tex				= 0;
		de::Random					rnd				(deStringHash(getName()));
		GradientShader				shader;
		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);

		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.

		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);

		// Fill render target with gradient.
		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);

		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int levelW = de::max(1, m_width >> ndx);
			int levelH = de::max(1, m_height >> ndx);

			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
			{
				int x = rnd.getInt(0, getWidth()	- levelW);
				int y = rnd.getInt(0, getHeight()	- levelH);

				glCopyTexImage2D(s_cubeMapFaces[face], ndx, m_internalFormat, x, y, levelW, levelH, 0);
			}
		}
	}

	deUint32 m_internalFormat;
};



// Basic CopyTexSubImage2D() with 2D texture usage
class BasicCopyTexSubImage2DCase : public TextureSpecCase
{
public:
	BasicCopyTexSubImage2DCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_2D, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
	{
	}

protected:
	void createTexture (void)
	{
		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
		tcu::TextureFormat			fmt				= m_texFormat;
		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
		int							numLevels		= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32					tex				= 0;
		tcu::TextureLevel			data			(fmt);
		de::Random					rnd				(deStringHash(getName()));
		GradientShader				shader;
		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);

		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_2D, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		// First specify full texture.
		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);

			Vec4	colorA		= randomVector<4>(rnd);
			Vec4	colorB		= randomVector<4>(rnd);
			int		cellSize	= rnd.getInt(2, 16);

			data.setSize(levelW, levelH);
			tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);

			glTexImage2D(GL_TEXTURE_2D, ndx, m_format, levelW, levelH, 0, m_format, m_dataType, data.getAccess().getDataPtr());
		}

		// Fill render target with gradient.
		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));

		// Re-specify parts of each level.
		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);

			int		w			= rnd.getInt(1, levelW);
			int		h			= rnd.getInt(1, levelH);
			int		xo			= rnd.getInt(0, levelW-w);
			int		yo			= rnd.getInt(0, levelH-h);

			int		x			= rnd.getInt(0, getWidth() - w);
			int		y			= rnd.getInt(0, getHeight() - h);

			glCopyTexSubImage2D(GL_TEXTURE_2D, ndx, xo, yo, x, y, w, h);
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
};

// Basic CopyTexSubImage2D() with cubemap usage
class BasicCopyTexSubImageCubeCase : public TextureSpecCase
{
public:
	BasicCopyTexSubImageCubeCase (Context& context, const char* name, const char* desc, deUint32 format, deUint32 dataType, deUint32 flags, int width, int height)
		: TextureSpecCase	(context, name, desc, TEXTURETYPE_CUBE, glu::mapGLTransferFormat(format, dataType), flags, width, height)
		, m_format			(format)
		, m_dataType		(dataType)
	{
	}

protected:
	void createTexture (void)
	{
		const tcu::RenderTarget&	renderTarget	= TestCase::m_context.getRenderContext().getRenderTarget();
		bool						targetHasRGB	= renderTarget.getPixelFormat().redBits > 0 && renderTarget.getPixelFormat().greenBits > 0 && renderTarget.getPixelFormat().blueBits > 0;
		bool						targetHasAlpha	= renderTarget.getPixelFormat().alphaBits > 0;
		tcu::TextureFormat			fmt				= m_texFormat;
		bool						texHasRGB		= fmt.order != tcu::TextureFormat::A;
		bool						texHasAlpha		= fmt.order == tcu::TextureFormat::RGBA || fmt.order == tcu::TextureFormat::LA || fmt.order == tcu::TextureFormat::A;
		int							numLevels		= (m_flags & MIPMAPS) ? de::max(deLog2Floor32(m_width), deLog2Floor32(m_height))+1 : 1;
		deUint32					tex				= 0;
		tcu::TextureLevel			data			(fmt);
		de::Random					rnd				(deStringHash(getName()));
		GradientShader				shader;
		deUint32					shaderID		= getCurrentContext()->createProgram(&shader);

		DE_ASSERT(m_width == m_height); // Non-square cubemaps are not supported by GLES2.

		if ((texHasRGB && !targetHasRGB) || (texHasAlpha && !targetHasAlpha))
			throw tcu::NotSupportedError("Copying from current framebuffer is not supported", "", __FILE__, __LINE__);

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);

			data.setSize(levelW, levelH);

			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
			{
				Vec4	colorA		= randomVector<4>(rnd);
				Vec4	colorB		= randomVector<4>(rnd);
				int		cellSize	= rnd.getInt(2, 16);

				tcu::fillWithGrid(data.getAccess(), cellSize, colorA, colorB);
				glTexImage2D(s_cubeMapFaces[face], ndx, m_format, levelW, levelH, 0, m_format, m_dataType, data.getAccess().getDataPtr());
			}
		}

		// Fill render target with gradient.
		sglr::drawQuad(*getCurrentContext(), shaderID, tcu::Vec3(-1.0f, -1.0f, 0.0f), tcu::Vec3(1.0f, 1.0f, 0.0f));

		// Re-specify parts of each face and level.
		for (int ndx = 0; ndx < numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_width >> ndx);
			int		levelH		= de::max(1, m_height >> ndx);

			for (int face = 0; face < DE_LENGTH_OF_ARRAY(s_cubeMapFaces); face++)
			{
				int		w			= rnd.getInt(1, levelW);
				int		h			= rnd.getInt(1, levelH);
				int		xo			= rnd.getInt(0, levelW-w);
				int		yo			= rnd.getInt(0, levelH-h);

				int		x			= rnd.getInt(0, getWidth() - w);
				int		y			= rnd.getInt(0, getHeight() - h);

				glCopyTexSubImage2D(s_cubeMapFaces[face], ndx, xo, yo, x, y, w, h);
			}
		}
	}

	deUint32	m_format;
	deUint32	m_dataType;
};

TextureSpecificationTests::TextureSpecificationTests (Context& context)
	: TestCaseGroup(context, "specification", "Texture Specification Tests")
{
}

TextureSpecificationTests::~TextureSpecificationTests (void)
{
}

void TextureSpecificationTests::init (void)
{
	struct
	{
		const char*	name;
		deUint32	format;
		deUint32	dataType;
	} texFormats[] =
	{
		{ "a8",			GL_ALPHA,			GL_UNSIGNED_BYTE },
		{ "l8",			GL_LUMINANCE,		GL_UNSIGNED_BYTE },
		{ "la88",		GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE },
		{ "rgb565",		GL_RGB,				GL_UNSIGNED_SHORT_5_6_5 },
		{ "rgb888",		GL_RGB,				GL_UNSIGNED_BYTE },
		{ "rgba4444",	GL_RGBA,			GL_UNSIGNED_SHORT_4_4_4_4 },
		{ "rgba5551",	GL_RGBA,			GL_UNSIGNED_SHORT_5_5_5_1 },
		{ "rgba8888",	GL_RGBA,			GL_UNSIGNED_BYTE }
	};

	// Basic TexImage2D usage.
	{
		tcu::TestCaseGroup* basicTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_teximage2d", "Basic glTexImage2D() usage");
		addChild(basicTexImageGroup);
		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
		{
			const char*	fmtName		= texFormats[formatNdx].name;
			deUint32	format		= texFormats[formatNdx].format;
			deUint32	dataType	= texFormats[formatNdx].dataType;
			const int	tex2DWidth	= 64;
			const int	tex2DHeight	= 128;
			const int	texCubeSize	= 64;

			basicTexImageGroup->addChild(new BasicTexImage2DCase	(m_context,	(string(fmtName) + "_2d").c_str(),		"",	format, dataType, MIPMAPS, tex2DWidth, tex2DHeight));
			basicTexImageGroup->addChild(new BasicTexImageCubeCase	(m_context,	(string(fmtName) + "_cube").c_str(),	"",	format, dataType, MIPMAPS, texCubeSize, texCubeSize));
		}
	}

	// Randomized TexImage2D order.
	{
		tcu::TestCaseGroup* randomTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "random_teximage2d", "Randomized glTexImage2D() usage");
		addChild(randomTexImageGroup);

		de::Random rnd(9);

		// 2D cases.
		for (int ndx = 0; ndx < 10; ndx++)
		{
			int		formatNdx	= rnd.getInt(0, DE_LENGTH_OF_ARRAY(texFormats)-1);
			int		width		= 1 << rnd.getInt(2, 8);
			int		height		= 1 << rnd.getInt(2, 8);

			randomTexImageGroup->addChild(new RandomOrderTexImage2DCase(m_context, (string("2d_") + de::toString(ndx)).c_str(), "", texFormats[formatNdx].format, texFormats[formatNdx].dataType, MIPMAPS, width, height));
		}

		// Cubemap cases.
		for (int ndx = 0; ndx < 10; ndx++)
		{
			int		formatNdx	= rnd.getInt(0, DE_LENGTH_OF_ARRAY(texFormats)-1);
			int		size		= 1 << rnd.getInt(2, 8);

			randomTexImageGroup->addChild(new RandomOrderTexImageCubeCase(m_context, (string("cube_") + de::toString(ndx)).c_str(), "", texFormats[formatNdx].format, texFormats[formatNdx].dataType, MIPMAPS, size, size));
		}
	}

	// TexImage2D unpack alignment.
	{
		tcu::TestCaseGroup* alignGroup = new tcu::TestCaseGroup(m_testCtx, "teximage2d_align", "glTexImage2D() unpack alignment tests");
		addChild(alignGroup);

		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_l8_4_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			MIPMAPS,	 4, 8, 8));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_l8_63_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 30, 1));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_l8_63_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 30, 2));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_l8_63_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 30, 4));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_l8_63_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 30, 8));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4444_51_1",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 30, 1));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4444_51_2",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 30, 2));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4444_51_4",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 30, 4));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba4444_51_8",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 30, 8));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb888_39_1",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 43, 1));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb888_39_2",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 43, 2));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb888_39_4",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 43, 4));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgb888_39_8",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 43, 8));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8888_47_1",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 27, 1));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8888_47_2",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 27, 2));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8888_47_4",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 27, 4));
		alignGroup->addChild(new TexImage2DAlignCase	(m_context, "2d_rgba8888_47_8",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 27, 8));

		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_l8_4_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			MIPMAPS,	 4, 4, 8));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_l8_63_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 63, 1));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_l8_63_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 63, 2));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_l8_63_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 63, 4));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_l8_63_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			0,			63, 63, 8));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4444_51_1",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 51, 1));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4444_51_2",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 51, 2));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4444_51_4",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 51, 4));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba4444_51_8",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	0,			51, 51, 8));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb888_39_1",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 39, 1));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb888_39_2",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 39, 2));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb888_39_4",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 39, 4));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgb888_39_8",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			0,			39, 39, 8));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8888_47_1",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 47, 1));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8888_47_2",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 47, 2));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8888_47_4",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 47, 4));
		alignGroup->addChild(new TexImageCubeAlignCase	(m_context, "cube_rgba8888_47_8",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			0,			47, 47, 8));
	}

	// Basic TexSubImage2D usage.
	{
		tcu::TestCaseGroup* basicTexSubImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_texsubimage2d", "Basic glTexSubImage2D() usage");
		addChild(basicTexSubImageGroup);
		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
		{
			const char*	fmtName		= texFormats[formatNdx].name;
			deUint32	format		= texFormats[formatNdx].format;
			deUint32	dataType	= texFormats[formatNdx].dataType;
			const int	tex2DWidth	= 64;
			const int	tex2DHeight	= 128;
			const int	texCubeSize	= 64;

			basicTexSubImageGroup->addChild(new BasicTexSubImage2DCase		(m_context,	(string(fmtName) + "_2d").c_str(),		"",	format, dataType, MIPMAPS, tex2DWidth, tex2DHeight));
			basicTexSubImageGroup->addChild(new BasicTexSubImageCubeCase	(m_context,	(string(fmtName) + "_cube").c_str(),	"",	format, dataType, MIPMAPS, texCubeSize, texCubeSize));
		}
	}

	// TexSubImage2D to empty texture.
	{
		tcu::TestCaseGroup* texSubImageEmptyTexGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage2d_empty_tex", "glTexSubImage2D() to texture that has storage but no data");
		addChild(texSubImageEmptyTexGroup);
		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(texFormats); formatNdx++)
		{
			const char*	fmtName		= texFormats[formatNdx].name;
			deUint32	format		= texFormats[formatNdx].format;
			deUint32	dataType	= texFormats[formatNdx].dataType;
			const int	tex2DWidth	= 64;
			const int	tex2DHeight	= 32;
			const int	texCubeSize	= 32;

			texSubImageEmptyTexGroup->addChild(new TexSubImage2DEmptyTexCase	(m_context,	(string(fmtName) + "_2d").c_str(),		"",	format, dataType, MIPMAPS, tex2DWidth, tex2DHeight));
			texSubImageEmptyTexGroup->addChild(new TexSubImageCubeEmptyTexCase	(m_context,	(string(fmtName) + "_cube").c_str(),	"",	format, dataType, MIPMAPS, texCubeSize, texCubeSize));
		}
	}

	// TexSubImage2D alignment cases.
	{
		tcu::TestCaseGroup* alignGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage2d_align", "glTexSubImage2D() unpack alignment tests");
		addChild(alignGroup);

		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_1_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 1));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_1_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 2));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_1_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 4));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_1_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 8));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_63_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 1));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_63_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 2));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_63_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 4));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_l8_63_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 8));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4444_51_1",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 1));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4444_51_2",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 2));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4444_51_4",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 4));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba4444_51_8",		"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 8));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb888_39_1",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 1));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb888_39_2",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 2));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb888_39_4",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 4));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgb888_39_8",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 8));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8888_47_1",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 1));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8888_47_2",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 2));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8888_47_4",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 4));
		alignGroup->addChild(new TexSubImage2DAlignCase		(m_context, "2d_rgba8888_47_8",		"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 8));

		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_1_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 1));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_1_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 2));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_1_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 4));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_1_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64, 13, 17,  1,  6, 8));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_63_1",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 1));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_63_2",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 2));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_63_4",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 4));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_l8_63_8",			"",	GL_LUMINANCE,	GL_UNSIGNED_BYTE,			64, 64,  1,  9, 63, 30, 8));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4444_51_1",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 1));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4444_51_2",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 2));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4444_51_4",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 4));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba4444_51_8",	"",	GL_RGBA,		GL_UNSIGNED_SHORT_4_4_4_4,	64, 64,  7, 29, 51, 30, 8));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb888_39_1",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 1));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb888_39_2",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 2));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb888_39_4",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 4));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgb888_39_8",		"",	GL_RGB,			GL_UNSIGNED_BYTE,			64, 64, 11,  8, 39, 43, 8));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8888_47_1",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 1));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8888_47_2",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 2));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8888_47_4",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 4));
		alignGroup->addChild(new TexSubImageCubeAlignCase	(m_context, "cube_rgba8888_47_8",	"",	GL_RGBA,		GL_UNSIGNED_BYTE,			64, 64, 10,  1, 47, 27, 8));
	}

	// Basic glCopyTexImage2D() cases
	{
		tcu::TestCaseGroup* copyTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_copyteximage2d", "Basic glCopyTexImage2D() usage");
		addChild(copyTexImageGroup);

		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_alpha",				"",	GL_ALPHA,			MIPMAPS,	128, 64));
		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_luminance",			"",	GL_LUMINANCE,		MIPMAPS,	128, 64));
		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	MIPMAPS,	128, 64));
		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_rgb",				"",	GL_RGB,				MIPMAPS,	128, 64));
		copyTexImageGroup->addChild(new BasicCopyTexImage2DCase		(m_context, "2d_rgba",				"",	GL_RGBA,			MIPMAPS,	128, 64));

		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_alpha",			"",	GL_ALPHA,			MIPMAPS,	64, 64));
		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_luminance",		"",	GL_LUMINANCE,		MIPMAPS,	64, 64));
		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	MIPMAPS,	64, 64));
		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_rgb",				"",	GL_RGB,				MIPMAPS,	64, 64));
		copyTexImageGroup->addChild(new BasicCopyTexImageCubeCase	(m_context, "cube_rgba",			"",	GL_RGBA,			MIPMAPS,	64, 64));
	}

	// Basic glCopyTexSubImage2D() cases
	{
		tcu::TestCaseGroup* copyTexSubImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_copytexsubimage2d", "Basic glCopyTexSubImage2D() usage");
		addChild(copyTexSubImageGroup);

		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_alpha",				"",	GL_ALPHA,			GL_UNSIGNED_BYTE, MIPMAPS, 128, 64));
		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_luminance",			"",	GL_LUMINANCE,		GL_UNSIGNED_BYTE, MIPMAPS, 128, 64));
		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE, MIPMAPS, 128, 64));
		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_rgb",				"",	GL_RGB,				GL_UNSIGNED_BYTE, MIPMAPS, 128, 64));
		copyTexSubImageGroup->addChild(new BasicCopyTexSubImage2DCase	(m_context, "2d_rgba",				"",	GL_RGBA,			GL_UNSIGNED_BYTE, MIPMAPS, 128, 64));

		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_alpha",			"",	GL_ALPHA,			GL_UNSIGNED_BYTE, MIPMAPS, 64, 64));
		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_luminance",		"",	GL_LUMINANCE,		GL_UNSIGNED_BYTE, MIPMAPS, 64, 64));
		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_luminance_alpha",	"",	GL_LUMINANCE_ALPHA,	GL_UNSIGNED_BYTE, MIPMAPS, 64, 64));
		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_rgb",				"",	GL_RGB,				GL_UNSIGNED_BYTE, MIPMAPS, 64, 64));
		copyTexSubImageGroup->addChild(new BasicCopyTexSubImageCubeCase	(m_context, "cube_rgba",			"",	GL_RGBA,			GL_UNSIGNED_BYTE, MIPMAPS, 64, 64));
	}
}

} // Functional
} // gles2
} // deqp