/*-------------------------------------------------------------------------
 * 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 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 "es31fTextureSpecificationTests.hpp"
#include "tcuTestLog.hpp"
#include "tcuImageCompare.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "gluStrUtil.hpp"
#include "gluTexture.hpp"
#include "gluTextureUtil.hpp"
#include "sglrContextUtil.hpp"
#include "sglrContextWrapper.hpp"
#include "sglrGLContext.hpp"
#include "sglrReferenceContext.hpp"
#include "glsTextureTestUtil.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"

// \todo [2012-04-29 pyry] Should be named SglrUtil
#include "es31fFboTestUtil.hpp"

#include "glwEnums.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{

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

enum
{
	VIEWPORT_WIDTH	= 256,
	VIEWPORT_HEIGHT	= 256
};

static inline int maxLevelCount (int size)
{
	return (int)deLog2Floor32(size)+1;
}

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::CubeFace getCubeFaceFromNdx (int ndx)
{
	switch (ndx)
	{
		case 0:	return tcu::CUBEFACE_POSITIVE_X;
		case 1:	return tcu::CUBEFACE_NEGATIVE_X;
		case 2:	return tcu::CUBEFACE_POSITIVE_Y;
		case 3:	return tcu::CUBEFACE_NEGATIVE_Y;
		case 4:	return tcu::CUBEFACE_POSITIVE_Z;
		case 5:	return tcu::CUBEFACE_NEGATIVE_Z;
		default:
			DE_ASSERT(false);
			return tcu::CUBEFACE_LAST;
	}
}

class TextureSpecCase : public TestCase, public sglr::ContextWrapper
{
public:
						TextureSpecCase			(Context& context, const char* name, const char* desc);
						~TextureSpecCase		(void);

	IterateResult		iterate					(void);

protected:
	virtual bool		checkExtensionSupport	(void)	{ return true; }

	virtual void		createTexture			(void)																= DE_NULL;
	virtual void		verifyTexture			(sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext)	= DE_NULL;

	// Utilities.
	void				renderTex				(tcu::Surface& dst, deUint32 program, int width, int height);
	void				readPixels				(tcu::Surface& dst, int x, int y, int width, int height);

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

TextureSpecCase::TextureSpecCase (Context& context, const char* name, const char* desc)
	: TestCase(context, name, desc)
{
}

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();

	if (renderTarget.getWidth() < VIEWPORT_WIDTH || renderTarget.getHeight() < VIEWPORT_HEIGHT)
		throw tcu::NotSupportedError("Too small viewport", "", __FILE__, __LINE__);

	if (!checkExtensionSupport())
		throw tcu::NotSupportedError("Extension not supported", "", __FILE__, __LINE__);

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

	// Contexts.
	sglr::GLContext					gles31Context	(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*)&gles31Context);
		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 GLES3.1 and reference contexts.
	for (int ndx = 0; ndx < 2; ndx++)
	{
		setContext(ndx ? (sglr::Context*)&refContext : (sglr::Context*)&gles31Context);
		createTexture();
		TCU_CHECK(glGetError() == GL_NO_ERROR);
	}

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

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

	// Verify results.
	verifyTexture(gles31Context, refContext);

	return STOP;
}

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

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

	sglr::drawQuad(*getCurrentContext(), program, 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());
}

class TextureCubeArraySpecCase : public TextureSpecCase
{
public:
							TextureCubeArraySpecCase	(Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int size, int depth, int numLevels);
							~TextureCubeArraySpecCase	(void);

protected:
	virtual bool			checkExtensionSupport		(void);
	virtual void			verifyTexture				(sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext);

	tcu::TextureFormat		m_texFormat;
	tcu::TextureFormatInfo	m_texFormatInfo;
	int						m_size;
	int						m_depth;
	int						m_numLevels;
};

TextureCubeArraySpecCase::TextureCubeArraySpecCase (Context& context, const char* name, const char* desc, const tcu::TextureFormat& format, int size, int depth, int numLevels)
	: TextureSpecCase		(context, name, desc)
	, m_texFormat			(format)
	, m_texFormatInfo		(tcu::getTextureFormatInfo(format))
	, m_size				(size)
	, m_depth				(depth)
	, m_numLevels			(numLevels)
{
}

TextureCubeArraySpecCase::~TextureCubeArraySpecCase (void)
{
}

bool TextureCubeArraySpecCase::checkExtensionSupport (void)
{
	const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));
	return supportsES32 || m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_cube_map_array");
}

void TextureCubeArraySpecCase::verifyTexture (sglr::GLContext& gles3Context, sglr::ReferenceContext& refContext)
{
	const glu::GLSLVersion	glslVersion		= glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
	TextureCubeArrayShader	shader			(glu::getSamplerCubeArrayType(m_texFormat), glu::TYPE_FLOAT_VEC4, glslVersion);
	deUint32				shaderIDgles	= gles3Context.createProgram(&shader);
	deUint32				shaderIDRef		= refContext.createProgram(&shader);

	shader.setTexScaleBias(m_texFormatInfo.lookupScale, m_texFormatInfo.lookupBias);

	// Set state.
	for (int ndx = 0; ndx < 2; ndx++)
	{
		sglr::Context* ctx = ndx ? static_cast<sglr::Context*>(&refContext) : static_cast<sglr::Context*>(&gles3Context);

		setContext(ctx);

		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER,	GL_NEAREST_MIPMAP_NEAREST);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER,	GL_NEAREST);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S,		GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T,		GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_R,		GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAX_LEVEL,	m_numLevels-1);
	}

	for (int layerFaceNdx = 0; layerFaceNdx < m_depth; layerFaceNdx++)
	{
		const int			layerNdx	= layerFaceNdx / 6;
		const tcu::CubeFace	face		= getCubeFaceFromNdx(layerFaceNdx % 6);
		bool				layerOk		= true;

		shader.setLayer(layerNdx);
		shader.setFace(face);

		for (int levelNdx = 0; levelNdx < m_numLevels; levelNdx++)
		{
			int				levelSize	= de::max(1, m_size	>> levelNdx);
			tcu::Surface	reference;
			tcu::Surface	result;

			if (levelSize <= 2)
				continue; // Fuzzy compare doesn't work for images this small.

			for (int ndx = 0; ndx < 2; ndx++)
			{
				tcu::Surface&	dst			= ndx ? reference									: result;
				sglr::Context*	ctx			= ndx ? static_cast<sglr::Context*>(&refContext)	: static_cast<sglr::Context*>(&gles3Context);
				deUint32		shaderID	= ndx ? shaderIDRef									: shaderIDgles;

				setContext(ctx);
				shader.setUniforms(*ctx, shaderID);
				renderTex(dst, shaderID, levelSize, levelSize);
			}

			const float		threshold		= 0.02f;
			string			levelStr		= de::toString(levelNdx);
			string			layerFaceStr	= de::toString(layerFaceNdx);
			string			name			= string("LayerFace") + layerFaceStr + "Level" + levelStr;
			string			desc			= string("Layer-face ") + layerFaceStr + ", Level " + levelStr;
			bool			isFaceOk		= tcu::fuzzyCompare(m_testCtx.getLog(), name.c_str(), desc.c_str(), reference, result, threshold,
																(levelNdx == 0 && layerFaceNdx == 0) == 0 ? tcu::COMPARE_LOG_RESULT : tcu::COMPARE_LOG_ON_ERROR);

			if (!isFaceOk)
			{
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
				layerOk = false;
				break;
			}
		}

		if (!layerOk)
			break;
	}
}

// Basic TexImage3D() with cube map array texture usage
class BasicTexImageCubeArrayCase : public TextureCubeArraySpecCase
{
public:
	BasicTexImageCubeArrayCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size, int numLayers)
		: TextureCubeArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, numLayers, maxLevelCount(size))
		, m_internalFormat			(internalFormat)
	{
	}

protected:
	void createTexture (void)
	{
		deUint32				tex			= 0;
		de::Random				rnd			(deStringHash(getName()));
		glu::TransferFormat		transferFmt	= glu::getTransferFormat(m_texFormat);
		tcu::TextureLevel		levelData	(glu::mapGLTransferFormat(transferFmt.format, transferFmt.dataType));

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

		for (int ndx = 0; ndx < m_numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_size	>> ndx);
			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);

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

			glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, ndx, m_internalFormat, levelW, levelW, m_depth, 0, transferFmt.format, transferFmt.dataType, levelData.getAccess().getDataPtr());
		}
	}

	deUint32 m_internalFormat;
};

// Basic glTexStorage3D() with cube map array texture usage
class BasicTexStorageCubeArrayCase : public TextureCubeArraySpecCase
{
public:
	BasicTexStorageCubeArrayCase (Context& context, const char* name, const char* desc, deUint32 internalFormat, int size, int numLayers, int numLevels)
		: TextureCubeArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, numLayers, numLevels)
		, m_internalFormat			(internalFormat)
	{
	}

protected:
	void createTexture (void)
	{
		deUint32				tex			= 0;
		de::Random				rnd			(deStringHash(getName()));
		glu::TransferFormat		transferFmt	= glu::getTransferFormat(m_texFormat);
		tcu::TextureLevel		levelData	(glu::mapGLTransferFormat(transferFmt.format, transferFmt.dataType));

		glGenTextures	(1, &tex);
		glBindTexture	(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
		glTexStorage3D	(GL_TEXTURE_CUBE_MAP_ARRAY, m_numLevels, m_internalFormat, m_size, m_size, m_depth);

		glPixelStorei	(GL_UNPACK_ALIGNMENT, 1);

		for (int ndx = 0; ndx < m_numLevels; ndx++)
		{
			int		levelW		= de::max(1, m_size	>> ndx);
			Vec4	gMin		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
			Vec4	gMax		= randomVector<4>(rnd, m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);

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

			glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, ndx, 0, 0, 0, levelW, levelW, m_depth, transferFmt.format, transferFmt.dataType, levelData.getAccess().getDataPtr());
		}
	}

	deUint32 m_internalFormat;
};

// Pixel buffer object cases.

// TexImage3D() cube map array from pixel buffer object.
class TexImageCubeArrayBufferCase : public TextureCubeArraySpecCase
{
public:
	TexImageCubeArrayBufferCase (Context&		context,
							   const char*	name,
							   const char*	desc,
							   deUint32		internalFormat,
							   int			size,
							   int			depth,
							   int			imageHeight,
							   int			rowLength,
							   int			skipImages,
							   int			skipRows,
							   int			skipPixels,
							   int			alignment,
							   int			offset)
		: TextureCubeArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, depth, 1)
		, m_internalFormat			(internalFormat)
		, m_imageHeight				(imageHeight)
		, m_rowLength				(rowLength)
		, m_skipImages				(skipImages)
		, m_skipRows				(skipRows)
		, m_skipPixels				(skipPixels)
		, m_alignment				(alignment)
		, m_offset					(offset)
	{
	}

protected:
	void createTexture (void)
	{
		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
		int						pixelSize		= m_texFormat.getPixelSize();
		int						rowLength		= m_rowLength > 0 ? m_rowLength : m_size;
		int						rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
		int						imageHeight		= m_imageHeight > 0 ? m_imageHeight : m_size;
		int						slicePitch		= imageHeight*rowPitch;
		deUint32				tex				= 0;
		deUint32				buf				= 0;
		vector<deUint8>			data;

		DE_ASSERT(m_numLevels == 1);

		// Fill data with grid.
		data.resize(slicePitch*(m_depth+m_skipImages) + m_offset);
		{
			Vec4	cScale		= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
			Vec4	cBias		= m_texFormatInfo.valueMin;
			Vec4	colorA		= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
			Vec4	colorB		= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;

			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_size, m_size, m_depth, rowPitch, slicePitch, &data[0] + m_skipImages*slicePitch + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), 4, colorA, colorB);
		}

		glGenBuffers(1, &buf);
		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
		glBufferData(GL_PIXEL_UNPACK_BUFFER, (int)data.size(), &data[0], GL_STATIC_DRAW);

		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	m_imageHeight);
		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	m_skipImages);
		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
		glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, m_internalFormat, m_size, m_size, m_depth, 0, transferFmt.format, transferFmt.dataType, (const void*)(deUintptr)m_offset);
	}

	deUint32	m_internalFormat;
	int			m_imageHeight;
	int			m_rowLength;
	int			m_skipImages;
	int			m_skipRows;
	int			m_skipPixels;
	int			m_alignment;
	int			m_offset;
};

// TexSubImage3D() cube map array PBO case.
class TexSubImageCubeArrayBufferCase : public TextureCubeArraySpecCase
{
public:
	TexSubImageCubeArrayBufferCase (Context&		context,
								 const char*	name,
								 const char*	desc,
								 deUint32		internalFormat,
								 int			size,
								 int			depth,
								 int			subX,
								 int			subY,
								 int			subZ,
								 int			subW,
								 int			subH,
								 int			subD,
								 int			imageHeight,
								 int			rowLength,
								 int			skipImages,
								 int			skipRows,
								 int			skipPixels,
								 int			alignment,
								 int			offset)
		: TextureCubeArraySpecCase	(context, name, desc, glu::mapGLInternalFormat(internalFormat), size, depth, 1)
		, m_internalFormat			(internalFormat)
		, m_subX					(subX)
		, m_subY					(subY)
		, m_subZ					(subZ)
		, m_subW					(subW)
		, m_subH					(subH)
		, m_subD					(subD)
		, m_imageHeight				(imageHeight)
		, m_rowLength				(rowLength)
		, m_skipImages				(skipImages)
		, m_skipRows				(skipRows)
		, m_skipPixels				(skipPixels)
		, m_alignment				(alignment)
		, m_offset					(offset)
	{
	}

protected:
	void createTexture (void)
	{
		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
		int						pixelSize		= m_texFormat.getPixelSize();
		deUint32				tex				= 0;
		deUint32				buf				= 0;
		vector<deUint8>			data;

		DE_ASSERT(m_numLevels == 1);

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

		// Fill with gradient.
		{
			int		rowPitch		= deAlign32(pixelSize*m_size,  4);
			int		slicePitch		= rowPitch*m_size;

			data.resize(slicePitch*m_depth);
			tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_size, m_size, m_depth, rowPitch, slicePitch, &data[0]), m_texFormatInfo.valueMin, m_texFormatInfo.valueMax);
		}

		glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, m_internalFormat, m_size, m_size, m_depth, 0, transferFmt.format, transferFmt.dataType, &data[0]);

		// Fill data with grid.
		{
			int		rowLength		= m_rowLength > 0 ? m_rowLength : m_subW;
			int		rowPitch		= deAlign32(rowLength*pixelSize, m_alignment);
			int		imageHeight		= m_imageHeight > 0 ? m_imageHeight : m_subH;
			int		slicePitch		= imageHeight*rowPitch;
			Vec4	cScale			= m_texFormatInfo.valueMax-m_texFormatInfo.valueMin;
			Vec4	cBias			= m_texFormatInfo.valueMin;
			Vec4	colorA			= Vec4(1.0f, 0.0f, 0.0f, 1.0f)*cScale + cBias;
			Vec4	colorB			= Vec4(0.0f, 1.0f, 0.0f, 1.0f)*cScale + cBias;

			data.resize(slicePitch*(m_depth+m_skipImages) + m_offset);
			tcu::fillWithGrid(tcu::PixelBufferAccess(m_texFormat, m_subW, m_subH, m_subD, rowPitch, slicePitch, &data[0] + m_skipImages*slicePitch + m_skipRows*rowPitch + m_skipPixels*pixelSize + m_offset), 4, colorA, colorB);
		}

		glGenBuffers(1, &buf);
		glBindBuffer(GL_PIXEL_UNPACK_BUFFER,	buf);
		glBufferData(GL_PIXEL_UNPACK_BUFFER,	(int)data.size(), &data[0], GL_STATIC_DRAW);

		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	m_imageHeight);
		glPixelStorei(GL_UNPACK_ROW_LENGTH,		m_rowLength);
		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	m_skipImages);
		glPixelStorei(GL_UNPACK_SKIP_ROWS,		m_skipRows);
		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	m_skipPixels);
		glPixelStorei(GL_UNPACK_ALIGNMENT,		m_alignment);
		glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, m_subX, m_subY, m_subZ, m_subW, m_subH, m_subD, transferFmt.format, transferFmt.dataType, (const void*)(deIntptr)m_offset);
	}

	deUint32	m_internalFormat;
	int			m_subX;
	int			m_subY;
	int			m_subZ;
	int			m_subW;
	int			m_subH;
	int			m_subD;
	int			m_imageHeight;
	int			m_rowLength;
	int			m_skipImages;
	int			m_skipRows;
	int			m_skipPixels;
	int			m_alignment;
	int			m_offset;
};

// TexImage3D() depth case.
class TexImageCubeArrayDepthCase : public TextureCubeArraySpecCase
{
public:
	TexImageCubeArrayDepthCase (Context&	context,
							  const char*	name,
							  const char*	desc,
							  deUint32		internalFormat,
							  int			imageSize,
							  int			numLayers)
		: TextureCubeArraySpecCase(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageSize, numLayers, maxLevelCount(imageSize))
		, m_internalFormat		(internalFormat)
	{
		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
	}

	void createTexture (void)
	{
		glu::TransferFormat	fmt			= glu::getTransferFormat(m_texFormat);
		deUint32			tex			= 0;
		tcu::TextureLevel	levelData	(glu::mapGLTransferFormat(fmt.format, fmt.dataType));

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		GLU_CHECK();

		for (int ndx = 0; ndx < m_numLevels; ndx++)
		{
			const int   levelW		= de::max(1, m_size >> ndx);
			const Vec4  gMin		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
			const Vec4  gMax		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);

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

			glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, ndx, m_internalFormat, levelW, levelW, m_depth, 0, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
		}
	}

	const deUint32 m_internalFormat;
};

// TexSubImage3D() depth case.
class TexSubImageCubeArrayDepthCase : public TextureCubeArraySpecCase
{
public:
	TexSubImageCubeArrayDepthCase (Context&		context,
								 const char*	name,
								 const char*	desc,
								 deUint32		internalFormat,
								 int			imageSize,
								 int			numLayers)
		: TextureCubeArraySpecCase(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageSize, numLayers, maxLevelCount(imageSize))
		, m_internalFormat		(internalFormat)
	{
		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
	}

	void createTexture (void)
	{
		glu::TransferFormat	fmt			= glu::getTransferFormat(m_texFormat);
		de::Random			rnd			(deStringHash(getName()));
		deUint32			tex			= 0;
		tcu::TextureLevel	levelData	(glu::mapGLTransferFormat(fmt.format, fmt.dataType));

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
		GLU_CHECK();

		// First specify full texture.
		for (int ndx = 0; ndx < m_numLevels; ndx++)
		{
			const int   levelW		= de::max(1, m_size >> ndx);
			const Vec4  gMin		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
			const Vec4  gMax		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);

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

			glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, ndx, m_internalFormat, levelW, levelW, m_depth, 0, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
		}

		// Re-specify parts of each level.
		for (int ndx = 0; ndx < m_numLevels; ndx++)
		{
			const int	levelW		= de::max(1, m_size >> ndx);

			const int	w			= rnd.getInt(1, levelW);
			const int	h			= rnd.getInt(1, levelW);
			const int	d			= rnd.getInt(1, m_depth);
			const int	x			= rnd.getInt(0, levelW-w);
			const int	y			= rnd.getInt(0, levelW-h);
			const int	z			= rnd.getInt(0, m_depth-d);

			const Vec4	colorA		= Vec4(2.0f, 1.5f, -1.0f, 2.0f);
			const Vec4	colorB		= Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
			const int	cellSize	= rnd.getInt(2, 16);

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

			glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, ndx, x, y, z, w, h, d, fmt.format, fmt.dataType, levelData.getAccess().getDataPtr());
		}
	}

	const deUint32 m_internalFormat;
};

// TexImage3D() depth case with pbo.
class TexImageCubeArrayDepthBufferCase : public TextureCubeArraySpecCase
{
public:
	TexImageCubeArrayDepthBufferCase (Context&	context,
									const char*	name,
									const char*	desc,
									deUint32	internalFormat,
									int			imageSize,
									int			numLayers)
		: TextureCubeArraySpecCase(context, name, desc, glu::mapGLInternalFormat(internalFormat), imageSize, numLayers, 1)
		, m_internalFormat		(internalFormat)
	{
		// we are interested in the behavior near [-2, 2], map it to visible range [0, 1]
		m_texFormatInfo.lookupBias = Vec4(0.25f, 0.0f, 0.0f, 1.0f);
		m_texFormatInfo.lookupScale = Vec4(0.5f, 1.0f, 1.0f, 0.0f);
	}

	void createTexture (void)
	{
		glu::TransferFormat		transferFmt		= glu::getTransferFormat(m_texFormat);
		int						pixelSize		= m_texFormat.getPixelSize();
		int						rowLength		= m_size;
		int						alignment		= 4;
		int						rowPitch		= deAlign32(rowLength*pixelSize, alignment);
		int						imageHeight		= m_size;
		int						slicePitch		= imageHeight*rowPitch;
		deUint32				tex				= 0;
		deUint32				buf				= 0;
		vector<deUint8>			data;

		DE_ASSERT(m_numLevels == 1);

		// Fill data with grid.
		data.resize(slicePitch*m_depth);
		{
			const Vec4 gMin = Vec4(-1.5f, -2.0f, 1.7f, -1.5f);
			const Vec4 gMax = Vec4(2.0f, 1.5f, -1.0f, 2.0f);

			tcu::fillWithComponentGradients(tcu::PixelBufferAccess(m_texFormat, m_size, m_size, m_depth, rowPitch, slicePitch, &data[0]), gMin, gMax);
		}

		glGenBuffers(1, &buf);
		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buf);
		glBufferData(GL_PIXEL_UNPACK_BUFFER, (int)data.size(), &data[0], GL_STATIC_DRAW);

		glPixelStorei(GL_UNPACK_IMAGE_HEIGHT,	imageHeight);
		glPixelStorei(GL_UNPACK_ROW_LENGTH,		rowLength);
		glPixelStorei(GL_UNPACK_SKIP_IMAGES,	0);
		glPixelStorei(GL_UNPACK_SKIP_ROWS,		0);
		glPixelStorei(GL_UNPACK_SKIP_PIXELS,	0);
		glPixelStorei(GL_UNPACK_ALIGNMENT,		alignment);

		glGenTextures(1, &tex);
		glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, tex);
		glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, m_internalFormat, m_size, m_size, m_depth, 0, transferFmt.format, transferFmt.dataType, DE_NULL);
		glDeleteBuffers(1, &buf);
	}

	const deUint32 m_internalFormat;
};

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

TextureSpecificationTests::~TextureSpecificationTests (void)
{
}

void TextureSpecificationTests::init (void)
{
	struct
	{
		const char*	name;
		deUint32	internalFormat;
	} colorFormats[] =
	{
		{ "rgba32f",			GL_RGBA32F,			},
		{ "rgba32i",			GL_RGBA32I,			},
		{ "rgba32ui",			GL_RGBA32UI,		},
		{ "rgba16f",			GL_RGBA16F,			},
		{ "rgba16i",			GL_RGBA16I,			},
		{ "rgba16ui",			GL_RGBA16UI,		},
		{ "rgba8",				GL_RGBA8,			},
		{ "rgba8i",				GL_RGBA8I,			},
		{ "rgba8ui",			GL_RGBA8UI,			},
		{ "srgb8_alpha8",		GL_SRGB8_ALPHA8,	},
		{ "rgb10_a2",			GL_RGB10_A2,		},
		{ "rgb10_a2ui",			GL_RGB10_A2UI,		},
		{ "rgba4",				GL_RGBA4,			},
		{ "rgb5_a1",			GL_RGB5_A1,			},
		{ "rgba8_snorm",		GL_RGBA8_SNORM,		},
		{ "rgb8",				GL_RGB8,			},
		{ "rgb565",				GL_RGB565,			},
		{ "r11f_g11f_b10f",		GL_R11F_G11F_B10F,	},
		{ "rgb32f",				GL_RGB32F,			},
		{ "rgb32i",				GL_RGB32I,			},
		{ "rgb32ui",			GL_RGB32UI,			},
		{ "rgb16f",				GL_RGB16F,			},
		{ "rgb16i",				GL_RGB16I,			},
		{ "rgb16ui",			GL_RGB16UI,			},
		{ "rgb8_snorm",			GL_RGB8_SNORM,		},
		{ "rgb8i",				GL_RGB8I,			},
		{ "rgb8ui",				GL_RGB8UI,			},
		{ "srgb8",				GL_SRGB8,			},
		{ "rgb9_e5",			GL_RGB9_E5,			},
		{ "rg32f",				GL_RG32F,			},
		{ "rg32i",				GL_RG32I,			},
		{ "rg32ui",				GL_RG32UI,			},
		{ "rg16f",				GL_RG16F,			},
		{ "rg16i",				GL_RG16I,			},
		{ "rg16ui",				GL_RG16UI,			},
		{ "rg8",				GL_RG8,				},
		{ "rg8i",				GL_RG8I,			},
		{ "rg8ui",				GL_RG8UI,			},
		{ "rg8_snorm",			GL_RG8_SNORM,		},
		{ "r32f",				GL_R32F,			},
		{ "r32i",				GL_R32I,			},
		{ "r32ui",				GL_R32UI,			},
		{ "r16f",				GL_R16F,			},
		{ "r16i",				GL_R16I,			},
		{ "r16ui",				GL_R16UI,			},
		{ "r8",					GL_R8,				},
		{ "r8i",				GL_R8I,				},
		{ "r8ui",				GL_R8UI,			},
		{ "r8_snorm",			GL_R8_SNORM,		}
	};

	static const struct
	{
		const char*	name;
		deUint32	internalFormat;
	} depthStencilFormats[] =
	{
		// Depth and stencil formats
		{ "depth_component32f",	GL_DEPTH_COMPONENT32F	},
		{ "depth_component24",	GL_DEPTH_COMPONENT24	},
		{ "depth_component16",	GL_DEPTH_COMPONENT16	},
		{ "depth32f_stencil8",	GL_DEPTH32F_STENCIL8	},
		{ "depth24_stencil8",	GL_DEPTH24_STENCIL8		}
	};

	// Basic TexImage3D usage.
	{
		tcu::TestCaseGroup* basicTexImageGroup = new tcu::TestCaseGroup(m_testCtx, "basic_teximage3d", "Basic glTexImage3D() usage");
		addChild(basicTexImageGroup);
		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
		{
			const char*	fmtName				= colorFormats[formatNdx].name;
			deUint32	format				= colorFormats[formatNdx].internalFormat;
			const int	texCubeArraySize	= 64;
			const int	texCubeArrayLayers	= 6;

			basicTexImageGroup->addChild(new BasicTexImageCubeArrayCase	(m_context,	(string(fmtName) + "_cube_array").c_str(),	"",	format, texCubeArraySize, texCubeArrayLayers));
		}
	}

	// glTexImage3D() pbo cases.
	{
		tcu::TestCaseGroup* pboGroup = new tcu::TestCaseGroup(m_testCtx, "teximage3d_pbo", "glTexImage3D() from PBO");
		addChild(pboGroup);

		// Parameter cases
		static const struct
		{
			const char*	name;
			deUint32	format;
			int			size;
			int			depth;
			int			imageHeight;
			int			rowLength;
			int			skipImages;
			int			skipRows;
			int			skipPixels;
			int			alignment;
			int			offset;
		} parameterCases[] =
		{
			{ "rgb8_offset",		GL_RGB8,	23,	6,	0,	0,	0,	0,	0,	1,	67 },
			{ "rgb8_alignment",		GL_RGB8,	23,	6,	0,	0,	0,	0,	0,	2,	0 },
			{ "rgb8_image_height",	GL_RGB8,	23,	6,	26,	0,	0,	0,	0,	4,	0 },
			{ "rgb8_row_length",	GL_RGB8,	23,	6,	0,	27,	0,	0,	0,	4,	0 },
			{ "rgb8_skip_images",	GL_RGB8,	23,	6,	0,	0,	3,	0,	0,	4,	0 },
			{ "rgb8_skip_rows",		GL_RGB8,	23,	6,	26,	0,	0,	3,	0,	4,	0 },
			{ "rgb8_skip_pixels",	GL_RGB8,	23,	6,	0,	25,	0,	0,	2,	4,	0 }
		};

		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
		{
			const string	fmtName				= colorFormats[formatNdx].name;
			const deUint32	format				= colorFormats[formatNdx].internalFormat;
			const int		texCubeArraySize	= 20;
			const int		texCubeDepth		= 6;

			pboGroup->addChild(new TexImageCubeArrayBufferCase	(m_context, (fmtName + "_cube_array").c_str(),	"", format, texCubeArraySize, texCubeDepth, 0, 0, 0, 0, 0, 4, 0));
		}

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(parameterCases); ndx++)
		{
			pboGroup->addChild(new TexImageCubeArrayBufferCase(m_context, (string(parameterCases[ndx].name) + "_cube_array").c_str(), "",
														parameterCases[ndx].format,
														parameterCases[ndx].size,
														parameterCases[ndx].depth,
														parameterCases[ndx].imageHeight,
														parameterCases[ndx].rowLength,
														parameterCases[ndx].skipImages,
														parameterCases[ndx].skipRows,
														parameterCases[ndx].skipPixels,
														parameterCases[ndx].alignment,
														parameterCases[ndx].offset));
		}
	}

	// glTexImage3D() depth cases.
	{
		tcu::TestCaseGroup* shadow3dGroup = new tcu::TestCaseGroup(m_testCtx, "teximage3d_depth", "glTexImage3D() with depth or depth/stencil format");
		addChild(shadow3dGroup);

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
		{
			const int	texCubeArraySize	= 64;
			const int	texCubeArrayDepth	= 6;

			shadow3dGroup->addChild(new TexImageCubeArrayDepthCase(m_context, (std::string(depthStencilFormats[ndx].name) + "_cube_array").c_str(), "", depthStencilFormats[ndx].internalFormat, texCubeArraySize, texCubeArrayDepth));
		}
	}

	// glTexImage3D() depth cases with pbo.
	{
		tcu::TestCaseGroup* shadow3dGroup = new tcu::TestCaseGroup(m_testCtx, "teximage3d_depth_pbo", "glTexImage3D() with depth or depth/stencil format with pbo");
		addChild(shadow3dGroup);

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
		{
			const int	texCubeArraySize	= 64;
			const int	texCubeArrayDepth	= 6;

			shadow3dGroup->addChild(new TexImageCubeArrayDepthBufferCase(m_context, (std::string(depthStencilFormats[ndx].name) + "_cube_array").c_str(), "", depthStencilFormats[ndx].internalFormat, texCubeArraySize, texCubeArrayDepth));
		}
	}

	// glTexSubImage3D() PBO cases.
	{
		tcu::TestCaseGroup* pboGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage3d_pbo", "glTexSubImage3D() pixel buffer object tests");
		addChild(pboGroup);

		static const struct
		{
			const char*	name;
			deUint32	format;
			int			size;
			int			depth;
			int			subX;
			int			subY;
			int			subZ;
			int			subW;
			int			subH;
			int			subD;
			int			imageHeight;
			int			rowLength;
			int			skipImages;
			int			skipRows;
			int			skipPixels;
			int			alignment;
			int			offset;
		} paramCases[] =
		{
			{ "rgb8_offset",		GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	0,	0,	0,	0,	0,	4,	67 },
			{ "rgb8_image_height",	GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	26,	0,	0,	0,	0,	4,	0 },
			{ "rgb8_row_length",	GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	0,	27,	0,	0,	0,	4,	0 },
			{ "rgb8_skip_images",	GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	0,	0,	3,	0,	0,	4,	0 },
			{ "rgb8_skip_rows",		GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	22,	0,	0,	3,	0,	4,	0 },
			{ "rgb8_skip_pixels",	GL_RGB8,	26, 12,	1,	2,	1,	23,	19,	8,	0,	25,	0,	0,	2,	4,	0 }
		};

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(colorFormats); ndx++)
		{
			pboGroup->addChild(new TexSubImageCubeArrayBufferCase(m_context, (std::string(colorFormats[ndx].name) + "_cube_array").c_str(), "",
														   colorFormats[ndx].internalFormat,
														   26,	// Size
														   12,	// Depth
														   1,	// Sub X
														   2,	// Sub Y
														   0,	// Sub Z
														   23,	// Sub W
														   19,	// Sub H
														   8,	// Sub D
														   0,	// Image height
														   0,	// Row length
														   0,	// Skip images
														   0,	// Skip rows
														   0,	// Skip pixels
														   4,	// Alignment
														   0	/* offset */));
		}

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(paramCases); ndx++)
		{
			pboGroup->addChild(new TexSubImageCubeArrayBufferCase(m_context, (std::string(paramCases[ndx].name) + "_cube_array").c_str(), "",
														   paramCases[ndx].format,
														   paramCases[ndx].size,
														   paramCases[ndx].depth,
														   paramCases[ndx].subX,
														   paramCases[ndx].subY,
														   paramCases[ndx].subZ,
														   paramCases[ndx].subW,
														   paramCases[ndx].subH,
														   paramCases[ndx].subD,
														   paramCases[ndx].imageHeight,
														   paramCases[ndx].rowLength,
														   paramCases[ndx].skipImages,
														   paramCases[ndx].skipRows,
														   paramCases[ndx].skipPixels,
														   paramCases[ndx].alignment,
														   paramCases[ndx].offset));
		}
	}

	// glTexSubImage3D() depth cases.
	{
		tcu::TestCaseGroup* shadow3dGroup = new tcu::TestCaseGroup(m_testCtx, "texsubimage3d_depth", "glTexSubImage3D() with depth or depth/stencil format");
		addChild(shadow3dGroup);

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthStencilFormats); ndx++)
		{
			const int	texCubeArraySize	= 57;
			const int	texCubeArrayLayers	= 6;

			shadow3dGroup->addChild(new TexSubImageCubeArrayDepthCase(m_context, (std::string(depthStencilFormats[ndx].name) + "_cube_array").c_str(), "", depthStencilFormats[ndx].internalFormat, texCubeArraySize, texCubeArrayLayers));
		}
	}

	// glTexStorage3D() cases.
	{
		tcu::TestCaseGroup* texStorageGroup = new tcu::TestCaseGroup(m_testCtx, "texstorage3d", "Basic glTexStorage3D() usage");
		addChild(texStorageGroup);

		// All formats.
		tcu::TestCaseGroup* formatGroup = new tcu::TestCaseGroup(m_testCtx, "format", "glTexStorage3D() with all formats");
		texStorageGroup->addChild(formatGroup);

		// Color formats.
		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(colorFormats); formatNdx++)
		{
			const char*	fmtName				= colorFormats[formatNdx].name;
			deUint32	internalFormat		= colorFormats[formatNdx].internalFormat;
			const int	texCubeArraySize	= 57;
			const int	texCubeArrayLayers	= 6;
			int			texCubeArrayLevels	= maxLevelCount(texCubeArraySize);

			formatGroup->addChild(new BasicTexStorageCubeArrayCase	(m_context, (string(fmtName) + "_cube_array").c_str(),	"", internalFormat, texCubeArraySize, texCubeArrayLayers, texCubeArrayLevels));
		}

		// Depth/stencil formats (only 2D texture array is supported).
		for (int formatNdx = 0; formatNdx < DE_LENGTH_OF_ARRAY(depthStencilFormats); formatNdx++)
		{
			const char*	fmtName				= depthStencilFormats[formatNdx].name;
			deUint32	internalFormat		= depthStencilFormats[formatNdx].internalFormat;
			const int	texCubeArraySize	= 57;
			const int	texCubeArrayLayers	= 6;
			int			texCubeArrayLevels	= maxLevelCount(texCubeArraySize);

			formatGroup->addChild(new BasicTexStorageCubeArrayCase	(m_context, (string(fmtName) + "_cube_array").c_str(),	"", internalFormat, texCubeArraySize, texCubeArrayLayers, texCubeArrayLevels));
		}

		// Sizes.
		static const struct
		{
			int				size;
			int				layers;
			int				levels;
		} texCubeArraySizes[] =
		{
			//	Sz	La	Le
			{	1,	6,	1 },
			{	2,	6,	2 },
			{	32,	6,	3 },
			{	64,	6,	4 },
			{	57,	12,	1 },
			{	57,	12,	2 },
			{	57,	12,	6 }
		};

		tcu::TestCaseGroup* sizeGroup = new tcu::TestCaseGroup(m_testCtx, "size", "glTexStorage3D() with various sizes");
		texStorageGroup->addChild(sizeGroup);

		for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(texCubeArraySizes); ndx++)
		{
			const deUint32		format		= GL_RGBA8;
			int					size		= texCubeArraySizes[ndx].size;
			int					layers		= texCubeArraySizes[ndx].layers;
			int					levels		= texCubeArraySizes[ndx].levels;
			string				name		= string("cube_array_") + de::toString(size) + "x" + de::toString(size) + "x" + de::toString(layers) + "_" + de::toString(levels) + "_levels";

			sizeGroup->addChild(new BasicTexStorageCubeArrayCase(m_context, name.c_str(), "", format, size, layers, levels));
		}
	}
}

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