/*-------------------------------------------------------------------------
 * 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 Buffer data upload tests.
 *//*--------------------------------------------------------------------*/

#include "es3fBufferWriteTests.hpp"
#include "glsBufferTestUtil.hpp"
#include "tcuTestLog.hpp"
#include "gluStrUtil.hpp"
#include "deMemory.h"
#include "deString.h"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deMath.h"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

#include <algorithm>
#include <list>

using std::set;
using std::vector;
using std::string;
using tcu::TestLog;
using tcu::IVec2;

namespace deqp
{
namespace gles3
{
namespace Functional
{

using namespace gls::BufferTestUtil;

struct DataStoreSpec
{
	DataStoreSpec (void)
		: target	(0)
		, usage		(0)
		, size		(0)
	{
	}

	DataStoreSpec (deUint32 target_, deUint32 usage_, int size_)
		: target	(target_)
		, usage		(usage_)
		, size		(size_)
	{
	}

	deUint32	target;
	deUint32	usage;
	int			size;
};

struct DataStoreSpecVecBuilder
{
	std::vector<DataStoreSpec>& list;

	DataStoreSpecVecBuilder (std::vector<DataStoreSpec>& list_)
		: list(list_)
	{
	}

	DataStoreSpecVecBuilder& operator<< (const DataStoreSpec& spec)
	{
		list.push_back(spec);
		return *this;
	}
};

struct RangeVecBuilder
{
	std::vector<tcu::IVec2>& list;

	RangeVecBuilder (std::vector<tcu::IVec2>& list_)
		: list(list_)
	{
	}

	RangeVecBuilder& operator<< (const tcu::IVec2& vec)
	{
		list.push_back(vec);
		return *this;
	}
};

template<typename Iterator>
static bool isRangeListValid (Iterator begin, Iterator end)
{
	if (begin != end)
	{
		// Fetch first.
		tcu::IVec2 prev = *begin;
		++begin;

		for (; begin != end; ++begin)
		{
			tcu::IVec2 cur = *begin;
			if (cur.x() <= prev.x() || cur.x() <= prev.x()+prev.y())
				return false;
			prev = cur;
		}
	}

	return true;
}

inline bool rangesIntersect (const tcu::IVec2& a, const tcu::IVec2& b)
{
	return de::inRange(a.x(), b.x(), b.x()+b.y()) || de::inRange(a.x()+a.y(), b.x(), b.x()+b.y()) ||
		   de::inRange(b.x(), a.x(), a.x()+a.y()) || de::inRange(b.x()+b.y(), a.x(), a.x()+a.y());
}

inline tcu::IVec2 unionRanges (const tcu::IVec2& a, const tcu::IVec2& b)
{
	DE_ASSERT(rangesIntersect(a, b));

	int start	= de::min(a.x(), b.x());
	int end		= de::max(a.x()+a.y(), b.x()+b.y());

	return tcu::IVec2(start, end-start);
}

//! Updates range list (start, len) with a new range.
std::vector<tcu::IVec2> addRangeToList (const std::vector<tcu::IVec2>& oldList, const tcu::IVec2& newRange)
{
	DE_ASSERT(newRange.y() > 0);

	std::vector<tcu::IVec2>					newList;
	std::vector<tcu::IVec2>::const_iterator	oldListIter	= oldList.begin();

	// Append ranges that end before the new range.
	for (; oldListIter != oldList.end() && oldListIter->x()+oldListIter->y() < newRange.x(); ++oldListIter)
		newList.push_back(*oldListIter);

	// Join any ranges that intersect new range
	{
		tcu::IVec2 curRange = newRange;
		while (oldListIter != oldList.end() && rangesIntersect(curRange, *oldListIter))
		{
			curRange = unionRanges(curRange, *oldListIter);
			++oldListIter;
		}

		newList.push_back(curRange);
	}

	// Append remaining ranges.
	for (; oldListIter != oldList.end(); oldListIter++)
		newList.push_back(*oldListIter);

	DE_ASSERT(isRangeListValid(newList.begin(), newList.end()));

	return newList;
}

class BasicBufferDataCase : public BufferCase
{
public:
	BasicBufferDataCase (Context& context, const char* name, const char* desc, deUint32 target, deUint32 usage, int size, VerifyType verify)
		: BufferCase	(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_target		(target)
		, m_usage		(usage)
		, m_size		(size)
		, m_verify		(verify)
	{
	}

	IterateResult iterate (void)
	{
		const deUint32			dataSeed	= deStringHash(getName()) ^ 0x125;
		BufferVerifier			verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
		ReferenceBuffer			refBuf;
		bool					isOk		= false;

		refBuf.setSize(m_size);
		fillWithRandomBytes(refBuf.getPtr(), m_size, dataSeed);

		deUint32 buf = genBuffer();
		glBindBuffer(m_target, buf);
		glBufferData(m_target, m_size, refBuf.getPtr(), m_usage);

		checkError();

		isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_size, m_target);

		deleteBuffer(buf);

		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								isOk ? "Pass"				: "Buffer verification failed");
		return STOP;
	}

private:
	deUint32		m_target;
	deUint32		m_usage;
	int				m_size;
	VerifyType		m_verify;
};

class RecreateBufferDataStoreCase : public BufferCase
{
public:
	RecreateBufferDataStoreCase (Context& context, const char* name, const char* desc, const DataStoreSpec* specs, int numSpecs, VerifyType verify)
		: BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_specs	(specs, specs+numSpecs)
		, m_verify	(verify)
	{
	}

	IterateResult iterate (void)
	{
		const deUint32			baseSeed	= deStringHash(getName()) ^ 0xbeef;
		BufferVerifier			verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
		ReferenceBuffer			refBuf;
		const deUint32			buf			= genBuffer();

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

		for (vector<DataStoreSpec>::const_iterator spec = m_specs.begin(); spec != m_specs.end(); spec++)
		{
			bool iterOk = false;

			refBuf.setSize(spec->size);
			fillWithRandomBytes(refBuf.getPtr(), spec->size, baseSeed ^ deInt32Hash(spec->size+spec->target+spec->usage));

			glBindBuffer(spec->target, buf);
			glBufferData(spec->target, spec->size, refBuf.getPtr(), spec->usage);

			checkError();

			iterOk = verifier.verify(buf, refBuf.getPtr(), 0, spec->size, spec->target);

			if (!iterOk)
			{
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
				break;
			}
		}

		deleteBuffer(buf);
		return STOP;
	}

private:
	std::vector<DataStoreSpec>	m_specs;
	VerifyType					m_verify;
};

class BasicBufferSubDataCase : public BufferCase
{
public:
	BasicBufferSubDataCase (Context& context, const char* name, const char* desc, deUint32 target, deUint32 usage, int size, int subDataOffs, int subDataSize, VerifyType verify)
		: BufferCase	(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_target		(target)
		, m_usage		(usage)
		, m_size		(size)
		, m_subDataOffs	(subDataOffs)
		, m_subDataSize	(subDataSize)
		, m_verify		(verify)
	{
		DE_ASSERT(de::inBounds(subDataOffs, 0, size) && de::inRange(subDataOffs+subDataSize, 0, size));
	}

	IterateResult iterate (void)
	{
		const deUint32			dataSeed	= deStringHash(getName());
		BufferVerifier			verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
		ReferenceBuffer			refBuf;
		bool					isOk		= false;

		refBuf.setSize(m_size);

		deUint32 buf = genBuffer();
		glBindBuffer(m_target, buf);

		// Initialize with glBufferData()
		fillWithRandomBytes(refBuf.getPtr(), m_size, dataSeed ^ 0x80354f);
		glBufferData(m_target, m_size, refBuf.getPtr(), m_usage);
		checkError();

		// Re-specify part of buffer
		fillWithRandomBytes(refBuf.getPtr()+m_subDataOffs, m_subDataSize, dataSeed ^ 0xfac425c);
		glBufferSubData(m_target, m_subDataOffs, m_subDataSize, refBuf.getPtr()+m_subDataOffs);

		isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_size, m_target);

		deleteBuffer(buf);

		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								isOk ? "Pass"				: "Buffer verification failed");
		return STOP;
	}

private:
	deUint32		m_target;
	deUint32		m_usage;
	int				m_size;
	int				m_subDataOffs;
	int				m_subDataSize;
	VerifyType		m_verify;
};

class SubDataToUndefinedCase : public BufferCase
{
public:
	SubDataToUndefinedCase (Context& context, const char* name, const char* desc, deUint32 target, deUint32 usage, int size, const tcu::IVec2* ranges, int numRanges, VerifyType verify)
		: BufferCase	(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_target		(target)
		, m_usage		(usage)
		, m_size		(size)
		, m_ranges		(ranges, ranges+numRanges)
		, m_verify		(verify)
	{
	}

	IterateResult iterate (void)
	{
		const deUint32			dataSeed	= deStringHash(getName());
		BufferVerifier			verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
		ReferenceBuffer			refBuf;
		bool					isOk		= true;
		std::vector<tcu::IVec2>	definedRanges;

		refBuf.setSize(m_size);

		deUint32 buf = genBuffer();
		glBindBuffer(m_target, buf);

		// Initialize storage with glBufferData()
		glBufferData(m_target, m_size, DE_NULL, m_usage);
		checkError();

		// Fill specified ranges with glBufferSubData()
		for (vector<tcu::IVec2>::const_iterator range = m_ranges.begin(); range != m_ranges.end(); range++)
		{
			fillWithRandomBytes(refBuf.getPtr()+range->x(), range->y(), dataSeed ^ deInt32Hash(range->x()+range->y()));
			glBufferSubData(m_target, range->x(), range->y(), refBuf.getPtr()+range->x());

			// Mark range as defined
			definedRanges = addRangeToList(definedRanges, *range);
		}

		// Verify defined parts
		for (vector<tcu::IVec2>::const_iterator range = definedRanges.begin(); range != definedRanges.end(); range++)
		{
			if (!verifier.verify(buf, refBuf.getPtr(), range->x(), range->y(), m_target))
				isOk = false;
		}

		deleteBuffer(buf);

		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								isOk ? "Pass"				: "Buffer verification failed");
		return STOP;
	}

private:
	deUint32				m_target;
	deUint32				m_usage;
	int						m_size;
	std::vector<tcu::IVec2>	m_ranges;
	VerifyType				m_verify;
};

class RandomBufferWriteCase : public BufferCase
{
public:
	RandomBufferWriteCase (Context& context, const char* name, const char* desc, deUint32 seed)
		: BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_seed		(seed)
		, m_verifier	(DE_NULL)
		, m_buffer		(0)
		, m_curSize		(0)
		, m_iterNdx		(0)
	{
	}

	~RandomBufferWriteCase (void)
	{
		delete m_verifier;
	}

	void init (void)
	{
		BufferCase::init();

		m_iterNdx	= 0;
		m_buffer	= genBuffer();
		m_curSize	= 0;
		m_verifier	= new BufferVerifier(m_renderCtx, m_testCtx.getLog(), VERIFY_AS_VERTEX_ARRAY);

		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	}

	void deinit (void)
	{
		deleteBuffer(m_buffer);
		m_refBuffer.setSize(0);

		delete m_verifier;
		m_verifier = DE_NULL;

		BufferCase::deinit();
	}

	IterateResult iterate (void)
	{
		// Parameters.
		const int	numIterations				= 5;
		const int	uploadsPerIteration			= 7;
		const int	minSize						= 12;
		const int	maxSize						= 32*1024;
		const float	respecifyProbability		= 0.07f;
		const float	respecifyDataProbability	= 0.2f;

		static const deUint32 bufferTargets[] =
		{
			GL_ARRAY_BUFFER,
			GL_COPY_READ_BUFFER,
			GL_COPY_WRITE_BUFFER,
			GL_ELEMENT_ARRAY_BUFFER,
			GL_PIXEL_PACK_BUFFER,
			GL_PIXEL_UNPACK_BUFFER,
			GL_TRANSFORM_FEEDBACK_BUFFER,
			GL_UNIFORM_BUFFER
		};

		static const deUint32 usageHints[] =
		{
			GL_STREAM_DRAW,
			GL_STREAM_READ,
			GL_STREAM_COPY,
			GL_STATIC_DRAW,
			GL_STATIC_READ,
			GL_STATIC_COPY,
			GL_DYNAMIC_DRAW,
			GL_DYNAMIC_READ,
			GL_DYNAMIC_COPY
		};

		bool		iterOk					= true;
		deUint32	curBoundTarget			= GL_NONE;
		de::Random	rnd						(m_seed ^ deInt32Hash(m_iterNdx) ^ 0xacf92e);

		m_testCtx.getLog() << TestLog::Section(string("Iteration") + de::toString(m_iterNdx+1), string("Iteration ") + de::toString(m_iterNdx+1) + " / " + de::toString(numIterations));

		for (int uploadNdx = 0; uploadNdx < uploadsPerIteration; uploadNdx++)
		{
			const deUint32	target		= bufferTargets[rnd.getInt(0, DE_LENGTH_OF_ARRAY(bufferTargets)-1)];
			const bool		respecify	= m_curSize == 0 || rnd.getFloat() < respecifyProbability;

			if (target != curBoundTarget)
			{
				glBindBuffer(target, m_buffer);
				curBoundTarget = target;
			}

			if (respecify)
			{
				const int		size			= rnd.getInt(minSize, maxSize);
				const deUint32	hint			= usageHints[rnd.getInt(0, DE_LENGTH_OF_ARRAY(usageHints)-1)];
				const bool		fillWithData	= rnd.getFloat() < respecifyDataProbability;

				m_refBuffer.setSize(size);
				if (fillWithData)
					fillWithRandomBytes(m_refBuffer.getPtr(), size, rnd.getUint32());

				glBufferData(target, size, fillWithData ? m_refBuffer.getPtr() : DE_NULL, hint);

				m_validRanges.clear();
				if (fillWithData)
					m_validRanges.push_back(tcu::IVec2(0, size));

				m_curSize = size;
			}
			else
			{
				// \note Non-uniform size distribution.
				const int	size	= de::clamp(deRoundFloatToInt32((float)m_curSize * deFloatPow(rnd.getFloat(0.0f, 0.7f), 3.0f)), minSize, m_curSize);
				const int	offset	= rnd.getInt(0, m_curSize-size);

				fillWithRandomBytes(m_refBuffer.getPtr()+offset, size, rnd.getUint32());
				glBufferSubData(target, offset, size, m_refBuffer.getPtr()+offset);

				m_validRanges = addRangeToList(m_validRanges, tcu::IVec2(offset, size));
			}
		}

		// Check error.
		{
			deUint32 err = glGetError();
			if (err != GL_NO_ERROR)
				throw tcu::TestError(string("Got ") + glu::getErrorStr(err).toString());
		}

		// Verify valid ranges.
		for (vector<IVec2>::const_iterator range = m_validRanges.begin(); range != m_validRanges.end(); range++)
		{
			const deUint32 targetHint = GL_ARRAY_BUFFER;
			if (!m_verifier->verify(m_buffer, m_refBuffer.getPtr(), range->x(), range->y(), targetHint))
			{
				m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer verification failed");
				iterOk = false;
				break;
			}
		}

		m_testCtx.getLog() << TestLog::EndSection;

		DE_ASSERT(iterOk || m_testCtx.getTestResult() != QP_TEST_RESULT_PASS);

		m_iterNdx += 1;
		return (iterOk && m_iterNdx < numIterations) ? CONTINUE : STOP;
	}

private:
	deUint32				m_seed;

	BufferVerifier*			m_verifier;
	deUint32				m_buffer;
	ReferenceBuffer			m_refBuffer;
	std::vector<tcu::IVec2>	m_validRanges;
	int						m_curSize;
	int						m_iterNdx;
};

BufferWriteTests::BufferWriteTests (Context& context)
	: TestCaseGroup(context, "write", "Buffer data upload tests")
{
}

BufferWriteTests::~BufferWriteTests (void)
{
}

void BufferWriteTests::init (void)
{
	static const deUint32 bufferTargets[] =
	{
		GL_ARRAY_BUFFER,
		GL_COPY_READ_BUFFER,
		GL_COPY_WRITE_BUFFER,
		GL_ELEMENT_ARRAY_BUFFER,
		GL_PIXEL_PACK_BUFFER,
		GL_PIXEL_UNPACK_BUFFER,
		GL_TRANSFORM_FEEDBACK_BUFFER,
		GL_UNIFORM_BUFFER
	};

	static const deUint32 usageHints[] =
	{
		GL_STREAM_DRAW,
		GL_STREAM_READ,
		GL_STREAM_COPY,
		GL_STATIC_DRAW,
		GL_STATIC_READ,
		GL_STATIC_COPY,
		GL_DYNAMIC_DRAW,
		GL_DYNAMIC_READ,
		GL_DYNAMIC_COPY
	};

	// .basic
	{
		tcu::TestCaseGroup* const basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic", "Basic upload with glBufferData()");
		addChild(basicGroup);

		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
		{
			for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usageHints); usageNdx++)
			{
				const deUint32		target	= bufferTargets[targetNdx];
				const deUint32		usage	= usageHints[usageNdx];
				const int			size	= 1020;
				const VerifyType	verify	= VERIFY_AS_VERTEX_ARRAY;
				const string		name	= string(getBufferTargetName(target)) + "_" + getUsageHintName(usage);

				basicGroup->addChild(new BasicBufferDataCase(m_context, name.c_str(), "", target, usage, size, verify));
			}
		}
	}

	// .recreate_store
	{
		tcu::TestCaseGroup* const recreateStoreGroup = new tcu::TestCaseGroup(m_testCtx, "recreate_store", "Data store recreate using glBufferData()");
		addChild(recreateStoreGroup);

#define RECREATE_STORE_CASE(NAME, DESC, SPECLIST)																												\
		do {																																				\
			std::vector<DataStoreSpec> specs;																												\
			DataStoreSpecVecBuilder builder(specs);																											\
			builder SPECLIST;																																\
			recreateStoreGroup->addChild(new RecreateBufferDataStoreCase(m_context, #NAME, DESC, &specs[0], (int)specs.size(), VERIFY_AS_VERTEX_ARRAY));	\
		} while (deGetFalse())

		RECREATE_STORE_CASE(identical_1, "Recreate with identical parameters",
			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996)
			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996)
			<< DataStoreSpec(GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996));

		RECREATE_STORE_CASE(identical_2, "Recreate with identical parameters",
			<< DataStoreSpec(GL_COPY_WRITE_BUFFER, GL_STATIC_DRAW, 72)
			<< DataStoreSpec(GL_COPY_WRITE_BUFFER, GL_STATIC_DRAW, 72)
			<< DataStoreSpec(GL_COPY_WRITE_BUFFER, GL_STATIC_DRAW, 72));

		RECREATE_STORE_CASE(different_target, "Recreate with different target",
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_DRAW, 504)
			<< DataStoreSpec(GL_COPY_READ_BUFFER,			GL_STATIC_DRAW, 504)
			<< DataStoreSpec(GL_COPY_WRITE_BUFFER,			GL_STATIC_DRAW, 504)
			<< DataStoreSpec(GL_ELEMENT_ARRAY_BUFFER,		GL_STATIC_DRAW, 504)
			<< DataStoreSpec(GL_PIXEL_PACK_BUFFER,			GL_STATIC_DRAW, 504)
			<< DataStoreSpec(GL_PIXEL_UNPACK_BUFFER,		GL_STATIC_DRAW, 504)
			<< DataStoreSpec(GL_TRANSFORM_FEEDBACK_BUFFER,	GL_STATIC_DRAW, 504)
			<< DataStoreSpec(GL_UNIFORM_BUFFER,				GL_STATIC_DRAW, 504)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_DRAW, 504));

		RECREATE_STORE_CASE(different_usage, "Recreate with different usage",
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		1644)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_COPY,		1644)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_READ,		1644)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_READ,		1644)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_DYNAMIC_COPY,	1644)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_COPY,		1644)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_DYNAMIC_READ,	1644)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_DYNAMIC_DRAW,	1644)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STATIC_DRAW,		1644)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		1644));

		RECREATE_STORE_CASE(different_size, "Recreate with different size",
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		1024)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		12)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		3327)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		92)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		123795)
			<< DataStoreSpec(GL_ARRAY_BUFFER,				GL_STREAM_DRAW,		571));

#undef RECREATE_STORE_CASE

		// Random cases.
		{
			const int			numRandomCases		= 4;
			const int			numUploadsPerCase	= 10;
			const int			minSize				= 12;
			const int			maxSize				= 65536;
			const VerifyType	verify				= VERIFY_AS_VERTEX_ARRAY;
			de::Random			rnd					(23921);

			for (int caseNdx = 0; caseNdx < numRandomCases; caseNdx++)
			{
				vector<DataStoreSpec> specs(numUploadsPerCase);

				for (vector<DataStoreSpec>::iterator spec = specs.begin(); spec != specs.end(); spec++)
				{
					spec->target	= bufferTargets[rnd.getInt(0, DE_LENGTH_OF_ARRAY(bufferTargets)-1)];
					spec->usage		= usageHints[rnd.getInt(0, DE_LENGTH_OF_ARRAY(usageHints)-1)];
					spec->size		= rnd.getInt(minSize, maxSize);
				}

				recreateStoreGroup->addChild(new RecreateBufferDataStoreCase(m_context, (string("random_") + de::toString(caseNdx+1)).c_str(), "", &specs[0], (int)specs.size(), verify));
			}
		}
	}

	// .basic_subdata
	{
		tcu::TestCaseGroup* const basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic_subdata", "Basic glBufferSubData() usage");
		addChild(basicGroup);

		for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
		{
			for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usageHints); usageNdx++)
			{
				const deUint32		target	= bufferTargets[targetNdx];
				const deUint32		usage	= usageHints[usageNdx];
				const int			size	= 1020;
				const VerifyType	verify	= VERIFY_AS_VERTEX_ARRAY;
				const string		name	= string(getBufferTargetName(target)) + "_" + getUsageHintName(usage);

				basicGroup->addChild(new BasicBufferDataCase(m_context, name.c_str(), "", target, usage, size, verify));
			}
		}
	}

	// .partial_specify
	{
		tcu::TestCaseGroup* const partialSpecifyGroup = new tcu::TestCaseGroup(m_testCtx, "partial_specify", "Partial buffer data specification with glBufferSubData()");
		addChild(partialSpecifyGroup);

#define PARTIAL_SPECIFY_CASE(NAME, DESC, TARGET, USAGE, SIZE, RANGELIST)																									\
		do {																																								\
			std::vector<tcu::IVec2> ranges;																																	\
			RangeVecBuilder builder(ranges);																																\
			builder RANGELIST;																																				\
			partialSpecifyGroup->addChild(new SubDataToUndefinedCase(m_context, #NAME, DESC, TARGET, USAGE, SIZE, &ranges[0], (int)ranges.size(), VERIFY_AS_VERTEX_ARRAY));	\
		} while (deGetFalse())

		PARTIAL_SPECIFY_CASE(whole_1, "Whole buffer specification with single glBufferSubData()", GL_ARRAY_BUFFER, GL_STATIC_DRAW, 996,
			<< IVec2(0, 996));
		PARTIAL_SPECIFY_CASE(whole_2, "Whole buffer specification with two calls", GL_UNIFORM_BUFFER, GL_DYNAMIC_READ, 1728,
			<< IVec2(729, 999)
			<< IVec2(0, 729));
		PARTIAL_SPECIFY_CASE(whole_3, "Whole buffer specification with three calls", GL_TRANSFORM_FEEDBACK_BUFFER, GL_STREAM_COPY, 1944,
			<< IVec2(0, 421)
			<< IVec2(1421, 523)
			<< IVec2(421, 1000));
		PARTIAL_SPECIFY_CASE(whole_4, "Whole buffer specification with three calls", GL_TRANSFORM_FEEDBACK_BUFFER, GL_STREAM_COPY, 1200,
			<< IVec2(0, 500)
			<< IVec2(429, 200)
			<< IVec2(513, 687));

		PARTIAL_SPECIFY_CASE(low_1, "Low part of buffer specified with single call", GL_ELEMENT_ARRAY_BUFFER, GL_DYNAMIC_DRAW, 1000,
			<< IVec2(0, 513));
		PARTIAL_SPECIFY_CASE(low_2, "Low part of buffer specified with two calls", GL_COPY_READ_BUFFER, GL_DYNAMIC_COPY, 996,
			<< IVec2(0, 98)
			<< IVec2(98, 511));
		PARTIAL_SPECIFY_CASE(low_3, "Low part of buffer specified with two calls", GL_COPY_READ_BUFFER, GL_DYNAMIC_COPY, 1200,
			<< IVec2(0, 591)
			<< IVec2(371, 400));

		PARTIAL_SPECIFY_CASE(high_1, "High part of buffer specified with single call", GL_COPY_WRITE_BUFFER, GL_STATIC_COPY, 1000,
			<< IVec2(500, 500));
		PARTIAL_SPECIFY_CASE(high_2, "High part of buffer specified with two calls", GL_TRANSFORM_FEEDBACK_BUFFER, GL_STREAM_DRAW, 1200,
			<< IVec2(600, 123)
			<< IVec2(723, 477));
		PARTIAL_SPECIFY_CASE(high_3, "High part of buffer specified with two calls", GL_PIXEL_PACK_BUFFER, GL_STREAM_READ, 1200,
			<< IVec2(600, 200)
			<< IVec2(601, 599));

		PARTIAL_SPECIFY_CASE(middle_1, "Middle part of buffer specified with single call", GL_PIXEL_UNPACK_BUFFER, GL_STREAM_READ, 2500,
			<< IVec2(1000, 799));
		PARTIAL_SPECIFY_CASE(middle_2, "Middle part of buffer specified with two calls", GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, 2500,
			<< IVec2(780, 220)
			<< IVec2(1000, 500));
		PARTIAL_SPECIFY_CASE(middle_3, "Middle part of buffer specified with two calls", GL_ARRAY_BUFFER, GL_STREAM_READ, 2500,
			<< IVec2(780, 321)
			<< IVec2(1000, 501));

#undef PARTIAL_SPECIFY_CASE
	}

	// .random
	{
		tcu::TestCaseGroup* const randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Randomized buffer data cases");
		addChild(randomGroup);

		for (int i = 0; i < 10; i++)
			randomGroup->addChild(new RandomBufferWriteCase(m_context, de::toString(i).c_str(), "", deInt32Hash(i)));
	}
}

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