/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) Module
 * -----------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Parametrized, long-running stress case.
 *
 * \todo [2013-06-27 nuutti] Do certain things in a cleaner and less
 *							 confusing way, such as the "redundant buffer
 *							 factor" thing in LongStressCase.
 *//*--------------------------------------------------------------------*/

#include "glsLongStressCase.hpp"
#include "tcuTestLog.hpp"
#include "tcuCommandLine.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVector.hpp"
#include "tcuVectorUtil.hpp"
#include "glsTextureTestUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "tcuStringTemplate.hpp"
#include "gluStrUtil.hpp"
#include "gluShaderProgram.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deString.h"
#include "deSharedPtr.hpp"
#include "deClock.h"

#include "glw.h"

#include <limits>
#include <vector>
#include <iomanip>
#include <map>
#include <iomanip>

using tcu::TestLog;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;
using tcu::IVec2;
using tcu::IVec3;
using tcu::IVec4;
using tcu::TextureLevel;
using tcu::TextureFormat;
using tcu::ConstPixelBufferAccess;
using tcu::CubeFace;
using de::SharedPtr;
using de::Random;
using de::toString;

using std::vector;
using std::string;
using std::map;

namespace deqp
{
namespace gls
{

using TextureTestUtil::TextureType;
using TextureTestUtil::TEXTURETYPE_2D;
using TextureTestUtil::TEXTURETYPE_CUBE;

static const float Mi = (float)(1<<20);

static const deUint32 bufferUsages[] =
{
	GL_STATIC_DRAW,
	GL_STREAM_DRAW,
	GL_DYNAMIC_DRAW,

	GL_STATIC_READ,
	GL_STREAM_READ,
	GL_DYNAMIC_READ,

	GL_STATIC_COPY,
	GL_STREAM_COPY,
	GL_DYNAMIC_COPY
};

static const deUint32 bufferUsagesGLES2[] =
{
	GL_STATIC_DRAW,
	GL_DYNAMIC_DRAW,
	GL_STREAM_DRAW
};

static const deUint32 bufferTargets[] =
{
	GL_ARRAY_BUFFER,
	GL_ELEMENT_ARRAY_BUFFER,

	GL_COPY_READ_BUFFER,
	GL_COPY_WRITE_BUFFER,
	GL_PIXEL_PACK_BUFFER,
	GL_PIXEL_UNPACK_BUFFER,
	GL_TRANSFORM_FEEDBACK_BUFFER,
	GL_UNIFORM_BUFFER
};

static const deUint32 bufferTargetsGLES2[] =
{
	GL_ARRAY_BUFFER,
	GL_ELEMENT_ARRAY_BUFFER
};

static inline int computePixelStore (const TextureFormat& format)
{
	const int pixelSize = format.getPixelSize();
	if (deIsPowerOfTwo32(pixelSize))
		return de::min(pixelSize, 8);
	else
		return 1;
}

static inline int getNumIterations (const tcu::TestContext& testCtx, const int defaultNumIterations)
{
	const int cmdLineVal = testCtx.getCommandLine().getTestIterationCount();
	return cmdLineVal == 0 ? defaultNumIterations : cmdLineVal;
}

static inline float triangleArea (const Vec2& a, const Vec2& b, const Vec2& c)
{
	const Vec2 ab = b-a;
	const Vec2 ac = c-a;
	return 0.5f * tcu::length(ab.x()*ac.y() - ab.y()*ac.x());
}

static inline string mangleShaderNames (const string& source, const string& manglingSuffix)
{
	map<string, string> m;
	m["NS"] = manglingSuffix;
	return tcu::StringTemplate(source.c_str()).specialize(m);
}

template <typename T, int N>
static inline T randomChoose (Random& rnd, const T (&arr)[N])
{
	return rnd.choose<T>(DE_ARRAY_BEGIN(arr), DE_ARRAY_END(arr));
}

static inline int nextDivisible (const int x, const int div)
{
	DE_ASSERT(x >= 0);
	DE_ASSERT(div >= 1);
	return x == 0 ? 0 : x-1 + div - (x-1) % div;
}

static inline string getTimeStr (const deUint64 seconds)
{
	const deUint64		m = seconds / 60;
	const deUint64		h = m / 60;
	const deUint64		d = h / 24;
	std::ostringstream	res;

	res << d << "d " << h%24 << "h " << m%60 << "m " << seconds%60 << "s";
	return res.str();
}

static inline string probabilityStr (const float prob)
{
	return prob == 0.0f ? "never"	:
		   prob == 1.0f ? "ALWAYS"	:
		   de::floatToString(prob*100.0f, 0) + "%";
}

static inline deUint32 randomBufferTarget (Random& rnd, const bool isGLES3)
{
	return isGLES3 ? randomChoose(rnd, bufferTargets) : randomChoose(rnd, bufferTargetsGLES2);
}

static inline deUint32 randomBufferUsage (Random& rnd, const bool isGLES3)
{
	return isGLES3 ? randomChoose(rnd, bufferUsages) : randomChoose(rnd, bufferUsagesGLES2);
}

static inline deUint32 cubeFaceToGLFace (tcu::CubeFace face)
{
	switch (face)
	{
		case tcu::CUBEFACE_NEGATIVE_X: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
		case tcu::CUBEFACE_POSITIVE_X: return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
		case tcu::CUBEFACE_NEGATIVE_Y: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
		case tcu::CUBEFACE_POSITIVE_Y: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
		case tcu::CUBEFACE_NEGATIVE_Z: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
		case tcu::CUBEFACE_POSITIVE_Z: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
		default:
			DE_ASSERT(false);
			return GL_NONE;
	}
}

#if defined(DE_DEBUG)
static inline bool isMatchingGLInternalFormat (const deUint32 internalFormat, const TextureFormat& texFormat)
{
	switch (internalFormat)
	{
		// Unsized formats.

		case GL_RGBA:				return texFormat.order == TextureFormat::RGBA &&
											   (texFormat.type == TextureFormat::UNORM_INT8			||
												texFormat.type == TextureFormat::UNORM_SHORT_4444	||
												texFormat.type == TextureFormat::UNORM_SHORT_5551);

		case GL_RGB:				return texFormat.order == TextureFormat::RGB &&
											   (texFormat.type == TextureFormat::UNORM_INT8			||
												texFormat.type == TextureFormat::UNORM_SHORT_565);

		case GL_LUMINANCE_ALPHA:	return texFormat.order == TextureFormat::LA && texFormat.type == TextureFormat::UNORM_INT8;
		case GL_LUMINANCE:			return texFormat.order == TextureFormat::L && texFormat.type == TextureFormat::UNORM_INT8;
		case GL_ALPHA:				return texFormat.order == TextureFormat::A && texFormat.type == TextureFormat::UNORM_INT8;

		// Sized formats.

		default:					return glu::mapGLInternalFormat(internalFormat) == texFormat;
	}
}
#endif // DE_DEBUG

static inline bool compileShader (const deUint32 shaderGL)
{
	glCompileShader(shaderGL);

	int success = GL_FALSE;
	glGetShaderiv(shaderGL, GL_COMPILE_STATUS, &success);

	return success == GL_TRUE;
}

static inline bool linkProgram (const deUint32 programGL)
{
	glLinkProgram(programGL);

	int success = GL_FALSE;
	glGetProgramiv(programGL, GL_LINK_STATUS, &success);

	return success == GL_TRUE;
}

static inline string getShaderInfoLog (const deUint32 shaderGL)
{
	int				infoLogLen = 0;
	vector<char>	infoLog;
	glGetShaderiv(shaderGL, GL_INFO_LOG_LENGTH, &infoLogLen);
	infoLog.resize(infoLogLen+1);
	glGetShaderInfoLog(shaderGL, (int)infoLog.size(), DE_NULL, &infoLog[0]);
	return &infoLog[0];
}

static inline string getProgramInfoLog (const deUint32 programGL)
{
	int				infoLogLen = 0;
	vector<char>	infoLog;
	glGetProgramiv(programGL, GL_INFO_LOG_LENGTH, &infoLogLen);
	infoLog.resize(infoLogLen+1);
	glGetProgramInfoLog(programGL, (int)infoLog.size(), DE_NULL, &infoLog[0]);
	return &infoLog[0];
}

namespace LongStressCaseInternal
{

// A hacky-ish class for drawing text on screen as GL quads.
class DebugInfoRenderer
{
public:
								DebugInfoRenderer		(const glu::RenderContext& ctx);
								~DebugInfoRenderer		(void) { delete m_prog; }

	void						drawInfo				(deUint64 secondsElapsed, int texMem, int maxTexMem, int bufMem, int maxBufMem, int iterNdx);

private:
								DebugInfoRenderer		(const DebugInfoRenderer&);
	DebugInfoRenderer&			operator=				(const DebugInfoRenderer&);

	void						render					(void);
	void						addTextToBuffer			(const string& text, int yOffset);

	const glu::RenderContext&	m_ctx;
	const glu::ShaderProgram*	m_prog;
	vector<float>				m_posBuf;
	vector<deUint16>			m_ndxBuf;
};

void DebugInfoRenderer::drawInfo (const deUint64 secondsElapsed, const int texMem, const int maxTexMem, const int bufMem, const int maxBufMem, const int iterNdx)
{
	const deUint64 m = secondsElapsed / 60;
	const deUint64 h = m / 60;
	const deUint64 d = h / 24;

	{
		std::ostringstream text;

		text << std::setw(2) << std::setfill('0') << d << ":"
			 << std::setw(2) << std::setfill('0') << h % 24 << ":"
			 << std::setw(2) << std::setfill('0') << m % 60 << ":"
			 << std::setw(2) << std::setfill('0') << secondsElapsed % 60;
		addTextToBuffer(text.str(), 0);
		text.str("");

		text << std::fixed << std::setprecision(2) << (float)texMem/Mi << "/" << (float)maxTexMem/Mi;
		addTextToBuffer(text.str(), 1);
		text.str("");

		text << std::fixed << std::setprecision(2) << (float)bufMem/Mi << "/" << (float)maxBufMem/Mi;
		addTextToBuffer(text.str(), 2);
		text.str("");

		text << std::setw(0) << iterNdx;
		addTextToBuffer(text.str(), 3);
	}

	render();
}

DebugInfoRenderer::DebugInfoRenderer (const glu::RenderContext& ctx)
	: m_ctx			(ctx)
	, m_prog		(DE_NULL)
{
	DE_ASSERT(!m_prog);
	m_prog = new glu::ShaderProgram(ctx, glu::makeVtxFragSources(
		"attribute highp vec2 a_pos;\n"
		"void main (void)\n"
		"{\n"
		"	gl_Position = vec4(a_pos, -1.0, 1.0);\n"
		"}\n",

		"void main(void)\n"
		"{\n"
		"	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
		"}\n"));
}

void DebugInfoRenderer::addTextToBuffer (const string& text, const int yOffset)
{
	static const char		characters[]	= "0123456789.:/";
	const int				numCharacters	= DE_LENGTH_OF_ARRAY(characters)-1; // \note -1 for null byte.
	const int				charWid			= 6;
	const int				charHei			= 6;
	static const string		charsStr		(characters);

	static const char font[numCharacters*charWid*charHei + 1]=
		" #### ""   #  "" #### ""##### ""   #  ""######"" #####""######"" #### "" #### ""      ""  ##  ""     #"
		"#    #""  ##  ""#    #""     #""  #   ""#     ""#     ""    # ""#    #""#    #""      ""  ##  ""    # "
		"#    #""   #  ""    # ""  ### "" #  # "" #### ""# ### ""   #  "" #### "" #####""      ""      ""   #  "
		"#    #""   #  ""   #  ""     #""######""     #""##   #""  #   ""#    #""     #""      ""  ##  ""  #   "
		"#    #""   #  ""  #   ""#    #""    # ""#    #""#    #"" #    ""#    #""   ## ""  ##  ""  ##  "" #    "
		" #### ""  ### ""######"" #### ""    # "" #### "" #### ""#     "" #### ""###   ""  ##  ""      ""#     ";

	for (int ndxInText = 0; ndxInText < (int)text.size(); ndxInText++)
	{
		const int ndxInCharset	= (int)charsStr.find(text[ndxInText]);
		DE_ASSERT(ndxInCharset < numCharacters);
		const int fontXStart	= ndxInCharset*charWid;

		for (int y = 0; y < charHei; y++)
		{
			float ay = -1.0f + (float)(y + 0 + yOffset*(charHei+2))*0.1f/(float)(charHei+2);
			float by = -1.0f + (float)(y + 1 + yOffset*(charHei+2))*0.1f/(float)(charHei+2);
			for (int x = 0; x < charWid; x++)
			{
				// \note Text is mirrored in x direction since on most(?) mobile devices the image is mirrored(?).
				float ax = 1.0f - (float)(x + 0 + ndxInText*(charWid+2))*0.1f/(float)(charWid+2);
				float bx = 1.0f - (float)(x + 1 + ndxInText*(charWid+2))*0.1f/(float)(charWid+2);

				if (font[y*numCharacters*charWid + fontXStart + x] != ' ')
				{
					const int vtxNdx = (int)m_posBuf.size()/2;

					m_ndxBuf.push_back(vtxNdx+0);
					m_ndxBuf.push_back(vtxNdx+1);
					m_ndxBuf.push_back(vtxNdx+2);

					m_ndxBuf.push_back(vtxNdx+2);
					m_ndxBuf.push_back(vtxNdx+1);
					m_ndxBuf.push_back(vtxNdx+3);

					m_posBuf.push_back(ax);
					m_posBuf.push_back(ay);

					m_posBuf.push_back(bx);
					m_posBuf.push_back(ay);

					m_posBuf.push_back(ax);
					m_posBuf.push_back(by);

					m_posBuf.push_back(bx);
					m_posBuf.push_back(by);
				}
			}
		}
	}
}

void DebugInfoRenderer::render (void)
{
	const int prog		= m_prog->getProgram();
	const int posloc	= glGetAttribLocation(prog, "a_pos");

	glUseProgram(prog);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glEnableVertexAttribArray(posloc);
	glVertexAttribPointer(posloc, 2, GL_FLOAT, 0, 0, &m_posBuf[0]);
	glDrawElements(GL_TRIANGLES, (int)m_ndxBuf.size(), GL_UNSIGNED_SHORT, &m_ndxBuf[0]);
	glDisableVertexAttribArray(posloc);

	m_posBuf.clear();
	m_ndxBuf.clear();
}

/*--------------------------------------------------------------------*//*!
 * \brief Texture object helper class
 *
 * Each Texture owns a GL texture object that is created when the Texture
 * is constructed and deleted when it's destructed. The class provides some
 * convenience interface functions to e.g. upload texture data to the GL.
 *
 * In addition, the class tracks the approximate amount of GL memory likely
 * used by the corresponding GL texture object; get this with
 * getApproxMemUsage(). Also, getApproxMemUsageDiff() returns N-M, where N
 * is the value that getApproxMemUsage() would return after a call to
 * setData() with arguments corresponding to those given to
 * getApproxMemUsageDiff(), and M is the value currently returned by
 * getApproxMemUsage(). This can be used to check if we need to free some
 * other memory before performing the setData() call, in case we have an
 * upper limit on the amount of memory we want to use.
 *//*--------------------------------------------------------------------*/
class Texture
{
public:
						Texture					(TextureType type);
						~Texture				(void);

	// Functions that may change the value returned by getApproxMemUsage().
	void				setData					(const ConstPixelBufferAccess& src, int width, int height, deUint32 internalFormat, bool useMipmap);

	// Functions that don't change the value returned by getApproxMemUsage().
	void				setSubData				(const ConstPixelBufferAccess& src, int xOff, int yOff, int width, int height) const;
	void				toUnit					(int unit) const;
	void				setFilter				(deUint32 min, deUint32 mag) const;
	void				setWrap					(deUint32 s, deUint32 t) const;

	int					getApproxMemUsage		(void) const { return m_dataSizeApprox; }
	int					getApproxMemUsageDiff	(int width, int height, deUint32 internalFormat, bool useMipmap) const;

private:
						Texture					(const Texture&); // Not allowed.
	Texture&			operator=				(const Texture&); // Not allowed.

	static deUint32		genTexture				(void) { deUint32 tex = 0; glGenTextures(1, &tex); return tex; }

	deUint32			getGLBindTarget			(void) const { DE_ASSERT(m_type == TEXTURETYPE_2D || m_type == TEXTURETYPE_CUBE); return m_type == TEXTURETYPE_2D ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP; }

	const TextureType	m_type;
	const deUint32		m_textureGL;

	int					m_numMipLevels;
	deUint32			m_internalFormat;
	int					m_dataSizeApprox;
};

Texture::Texture (const TextureType type)
	: m_type			(type)
	, m_textureGL		(genTexture())
	, m_numMipLevels	(0)
	, m_internalFormat	(0)
	, m_dataSizeApprox	(0)
{
}

Texture::~Texture (void)
{
	glDeleteTextures(1, &m_textureGL);
}

int Texture::getApproxMemUsageDiff (const int width, const int height, const deUint32 internalFormat, const bool useMipmap) const
{
	const int	numLevels				= useMipmap ? deLog2Floor32(de::max(width, height))+1 : 1;
	const int	pixelSize				= internalFormat == GL_RGBA		? 4
										: internalFormat == GL_RGB		? 3
										: internalFormat == GL_ALPHA	? 1
										: glu::mapGLInternalFormat(internalFormat).getPixelSize();
	int			memUsageApproxAfter		= 0;

	for (int level = 0; level < numLevels; level++)
		memUsageApproxAfter += de::max(1, width>>level) * de::max(1, height>>level) * pixelSize * (m_type == TEXTURETYPE_CUBE ? 6 : 1);

	return memUsageApproxAfter - getApproxMemUsage();
}

void Texture::setData (const ConstPixelBufferAccess& src, const int width, const int height, const deUint32 internalFormat, const bool useMipmap)
{
	DE_ASSERT(m_type != TEXTURETYPE_CUBE || width == height);
	DE_ASSERT(!useMipmap || (deIsPowerOfTwo32(width) && deIsPowerOfTwo32(height)));

	const TextureFormat&		format		= src.getFormat();
	const glu::TransferFormat	transfer	= glu::getTransferFormat(format);

	m_numMipLevels = useMipmap ? deLog2Floor32(de::max(width, height))+1 : 1;

	m_internalFormat = internalFormat;
	m_dataSizeApprox = width * height * format.getPixelSize() * (m_type == TEXTURETYPE_CUBE ? 6 : 1);

	DE_ASSERT(src.getRowPitch() == format.getPixelSize()*src.getWidth());
	DE_ASSERT(isMatchingGLInternalFormat(internalFormat, format));
	DE_ASSERT(width <= src.getWidth() && height <= src.getHeight());

	glPixelStorei(GL_UNPACK_ALIGNMENT, computePixelStore(format));

	if (m_type == TEXTURETYPE_2D)
	{
		m_dataSizeApprox = 0;

		glBindTexture(GL_TEXTURE_2D, m_textureGL);
		for (int level = 0; level < m_numMipLevels; level++)
		{
			const int levelWid = de::max(1, width>>level);
			const int levelHei = de::max(1, height>>level);
			m_dataSizeApprox += levelWid * levelHei * format.getPixelSize();
			glTexImage2D(GL_TEXTURE_2D, level, internalFormat, levelWid, levelHei, 0, transfer.format, transfer.dataType, src.getDataPtr());
		}
	}
	else if (m_type == TEXTURETYPE_CUBE)
	{
		m_dataSizeApprox = 0;

		glBindTexture(GL_TEXTURE_CUBE_MAP, m_textureGL);
		for (int level = 0; level < m_numMipLevels; level++)
		{
			const int levelWid = de::max(1, width>>level);
			const int levelHei = de::max(1, height>>level);
			m_dataSizeApprox += 6 * levelWid * levelHei * format.getPixelSize();
			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
				glTexImage2D(cubeFaceToGLFace((CubeFace)face), level, internalFormat, levelWid, levelHei, 0, transfer.format, transfer.dataType, src.getDataPtr());
		}
	}
	else
		DE_ASSERT(false);
}

void Texture::setSubData (const ConstPixelBufferAccess& src, const int xOff, const int yOff, const int width, const int height) const
{
	const TextureFormat&		format		= src.getFormat();
	const glu::TransferFormat	transfer	= glu::getTransferFormat(format);

	DE_ASSERT(src.getRowPitch() == format.getPixelSize()*src.getWidth());
	DE_ASSERT(isMatchingGLInternalFormat(m_internalFormat, format));
	DE_ASSERT(width <= src.getWidth() && height <= src.getHeight());

	glPixelStorei(GL_UNPACK_ALIGNMENT, computePixelStore(format));

	if (m_type == TEXTURETYPE_2D)
	{
		glBindTexture(GL_TEXTURE_2D, m_textureGL);
		for (int level = 0; level < m_numMipLevels; level++)
			glTexSubImage2D(GL_TEXTURE_2D, level, xOff>>level, yOff>>level, de::max(1, width>>level), de::max(1, height>>level), transfer.format, transfer.dataType, src.getDataPtr());
	}
	else if (m_type == TEXTURETYPE_CUBE)
	{
		glBindTexture(GL_TEXTURE_CUBE_MAP, m_textureGL);
		for (int level = 0; level < m_numMipLevels; level++)
		{
			for (int face = 0; face < tcu::CUBEFACE_LAST; face++)
				glTexSubImage2D(cubeFaceToGLFace((CubeFace)face), level, xOff>>level, yOff>>level, de::max(1, width>>level), de::max(1, height>>level), transfer.format, transfer.dataType, src.getDataPtr());
		}
	}
	else
		DE_ASSERT(false);
}

void Texture::setFilter (const deUint32 min, const deUint32 mag) const
{
	glBindTexture(getGLBindTarget(), m_textureGL);
	glTexParameteri(getGLBindTarget(), GL_TEXTURE_MIN_FILTER, min);
	glTexParameteri(getGLBindTarget(), GL_TEXTURE_MAG_FILTER, mag);
}

void Texture::setWrap (const deUint32 s, const deUint32 t) const
{
	glBindTexture(getGLBindTarget(), m_textureGL);
	glTexParameteri(getGLBindTarget(), GL_TEXTURE_WRAP_S, s);
	glTexParameteri(getGLBindTarget(), GL_TEXTURE_WRAP_T, t);
}

void Texture::toUnit (const int unit) const
{
	glActiveTexture(GL_TEXTURE0 + unit);
	glBindTexture(getGLBindTarget(), m_textureGL);
}

/*--------------------------------------------------------------------*//*!
 * \brief Buffer object helper class
 *
 * Each Buffer owns a GL buffer object that is created when the Buffer
 * is constructed and deleted when it's destructed. The class provides some
 * convenience interface functions to e.g. upload buffer data to the GL.
 *
 * In addition, the class tracks the approximate amount of GL memory,
 * similarly to the Texture class (see above). The getApproxMemUsageDiff()
 * is also analoguous.
 *//*--------------------------------------------------------------------*/
class Buffer
{
public:
						Buffer					(void);
						~Buffer					(void);

	// Functions that may change the value returned by getApproxMemUsage().
	template <typename T>
	void				setData					(const vector<T>& src, const deUint32 target, const deUint32 usage) { setData(&src[0], (int)(src.size()*sizeof(T)), target, usage); }
	void				setData					(const void* src, int size, deUint32 target, deUint32 usage);

	// Functions that don't change the value returned by getApproxMemUsage().
	template <typename T>
	void				setSubData				(const vector<T>& src, const int offsetElems, const int numElems, const deUint32 target) { setSubData(&src[offsetElems], offsetElems*(int)sizeof(T), numElems*(int)sizeof(T), target); }
	void				setSubData				(const void* src, int offsetBytes, int sizeBytes, deUint32 target) const;
	void				bind					(const deUint32 target) const { glBindBuffer(target, m_bufferGL); }

	int					getApproxMemUsage		(void) const { return m_dataSizeApprox; }
	template <typename T>
	int					getApproxMemUsageDiff	(const vector<T>& src) const { return getApproxMemUsageDiff((int)(src.size()*sizeof(T))); }
	int					getApproxMemUsageDiff	(const int sizeBytes) const { return sizeBytes - getApproxMemUsage(); }

private:
						Buffer					(const Buffer&); // Not allowed.
	Buffer&				operator=				(const Buffer&); // Not allowed.

	static deUint32		genBuffer				(void) { deUint32 buf = 0; glGenBuffers(1, &buf); return buf; }

	const deUint32		m_bufferGL;
	int					m_dataSizeApprox;
};

Buffer::Buffer (void)
	: m_bufferGL		(genBuffer())
	, m_dataSizeApprox	(0)
{
}

Buffer::~Buffer (void)
{
	glDeleteBuffers(1, &m_bufferGL);
}

void Buffer::setData (const void* const src, const int size, const deUint32 target, const deUint32 usage)
{
	bind(target);
	glBufferData(target, size, src, usage);
	glBindBuffer(target, 0);

	m_dataSizeApprox = size;
}

void Buffer::setSubData (const void* const src, const int offsetBytes, const int sizeBytes, const deUint32 target) const
{
	bind(target);
	glBufferSubData(target, offsetBytes, sizeBytes, src);
	glBindBuffer(target, 0);
}

class Program
{
public:
						Program					(void);
						~Program				(void);

	void				setSources				(const string& vertSource, const string& fragSource);
	void				build					(TestLog& log);
	void				use						(void) const { DE_ASSERT(m_isBuilt); glUseProgram(m_programGL); }
	void				setRandomUniforms		(const vector<VarSpec>& uniforms, const string& shaderNameManglingSuffix, Random& rnd) const;
	void				setAttribute			(const Buffer& attrBuf, int attrBufOffset, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const;
	void				setAttributeClientMem	(const void* attrData, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const;
	void				disableAttributeArray	(const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const;

private:
						Program				(const Program&); // Not allowed.
	Program&			operator=			(const Program&); // Not allowed.

	string				m_vertSource;
	string				m_fragSource;

	const deUint32		m_vertShaderGL;
	const deUint32		m_fragShaderGL;
	const deUint32		m_programGL;
	bool				m_hasSources;
	bool				m_isBuilt;
};

Program::Program (void)
	: m_vertShaderGL	(glCreateShader(GL_VERTEX_SHADER))
	, m_fragShaderGL	(glCreateShader(GL_FRAGMENT_SHADER))
	, m_programGL		(glCreateProgram())
	, m_hasSources		(false)
	, m_isBuilt			(false)
{
	glAttachShader(m_programGL, m_vertShaderGL);
	glAttachShader(m_programGL, m_fragShaderGL);
}

Program::~Program (void)
{
	glDeleteShader(m_vertShaderGL);
	glDeleteShader(m_fragShaderGL);
	glDeleteProgram(m_programGL);
}

void Program::setSources (const string& vertSource, const string& fragSource)
{
	const char* const vertSourceCstr = vertSource.c_str();
	const char* const fragSourceCstr = fragSource.c_str();

	m_vertSource = vertSource;
	m_fragSource = fragSource;

	// \note In GLES2 api the source parameter type lacks one const.
	glShaderSource(m_vertShaderGL, 1, (const char**)&vertSourceCstr, DE_NULL);
	glShaderSource(m_fragShaderGL, 1, (const char**)&fragSourceCstr, DE_NULL);

	m_hasSources = true;
}

void Program::build (TestLog& log)
{
	DE_ASSERT(m_hasSources);

	const bool vertCompileOk	= compileShader(m_vertShaderGL);
	const bool fragCompileOk	= compileShader(m_fragShaderGL);
	const bool attemptLink		= vertCompileOk && fragCompileOk;
	const bool linkOk			= attemptLink && linkProgram(m_programGL);

	if (!(vertCompileOk && fragCompileOk && linkOk))
	{
		log << TestLog::ShaderProgram(linkOk, attemptLink ? getProgramInfoLog(m_programGL) : string(""))
			<< TestLog::Shader(QP_SHADER_TYPE_VERTEX, m_vertSource, vertCompileOk, getShaderInfoLog(m_vertShaderGL))
			<< TestLog::Shader(QP_SHADER_TYPE_FRAGMENT, m_fragSource, fragCompileOk, getShaderInfoLog(m_fragShaderGL))
			<< TestLog::EndShaderProgram;

		throw tcu::TestError("Program build failed");
	}

	m_isBuilt = true;
}

void Program::setRandomUniforms (const vector<VarSpec>& uniforms, const string& shaderNameManglingSuffix, Random& rnd) const
{
	use();

	for (int unifNdx = 0; unifNdx < (int)uniforms.size(); unifNdx++)
	{
		const VarSpec&	spec			= uniforms[unifNdx];
		const int		typeScalarSize	= glu::getDataTypeScalarSize(spec.type);
		const int		location		= glGetUniformLocation(m_programGL, mangleShaderNames(spec.name, shaderNameManglingSuffix).c_str());
		if (location < 0)
			continue;

		if (glu::isDataTypeFloatOrVec(spec.type))
		{
			float val[4];
			for (int i = 0; i < typeScalarSize; i++)
				val[i] = rnd.getFloat(spec.minValue.f[i], spec.maxValue.f[i]);

			switch (spec.type)
			{
				case glu::TYPE_FLOAT:		glUniform1f(location, val[0]);							break;
				case glu::TYPE_FLOAT_VEC2:	glUniform2f(location, val[0], val[1]);					break;
				case glu::TYPE_FLOAT_VEC3:	glUniform3f(location, val[0], val[1], val[2]);			break;
				case glu::TYPE_FLOAT_VEC4:	glUniform4f(location, val[0], val[1], val[2], val[3]);	break;
				default: DE_ASSERT(false);
			}
		}
		else if (glu::isDataTypeMatrix(spec.type))
		{
			float val[4*4];
			for (int i = 0; i < typeScalarSize; i++)
				val[i] = rnd.getFloat(spec.minValue.f[i], spec.maxValue.f[i]);

			switch (spec.type)
			{
				case glu::TYPE_FLOAT_MAT2:		glUniformMatrix2fv		(location, 1, GL_FALSE, &val[0]); break;
				case glu::TYPE_FLOAT_MAT3:		glUniformMatrix3fv		(location, 1, GL_FALSE, &val[0]); break;
				case glu::TYPE_FLOAT_MAT4:		glUniformMatrix4fv		(location, 1, GL_FALSE, &val[0]); break;
				case glu::TYPE_FLOAT_MAT2X3:	glUniformMatrix2x3fv	(location, 1, GL_FALSE, &val[0]); break;
				case glu::TYPE_FLOAT_MAT2X4:	glUniformMatrix2x4fv	(location, 1, GL_FALSE, &val[0]); break;
				case glu::TYPE_FLOAT_MAT3X2:	glUniformMatrix3x2fv	(location, 1, GL_FALSE, &val[0]); break;
				case glu::TYPE_FLOAT_MAT3X4:	glUniformMatrix3x4fv	(location, 1, GL_FALSE, &val[0]); break;
				case glu::TYPE_FLOAT_MAT4X2:	glUniformMatrix4x2fv	(location, 1, GL_FALSE, &val[0]); break;
				case glu::TYPE_FLOAT_MAT4X3:	glUniformMatrix4x3fv	(location, 1, GL_FALSE, &val[0]); break;
				default: DE_ASSERT(false);
			}
		}
		else if (glu::isDataTypeIntOrIVec(spec.type))
		{
			int val[4];
			for (int i = 0; i < typeScalarSize; i++)
				val[i] = rnd.getInt(spec.minValue.i[i], spec.maxValue.i[i]);

			switch (spec.type)
			{
				case glu::TYPE_INT:			glUniform1i(location, val[0]);							break;
				case glu::TYPE_INT_VEC2:	glUniform2i(location, val[0], val[1]);					break;
				case glu::TYPE_INT_VEC3:	glUniform3i(location, val[0], val[1], val[2]);			break;
				case glu::TYPE_INT_VEC4:	glUniform4i(location, val[0], val[1], val[2], val[3]);	break;
				default: DE_ASSERT(false);
			}
		}
		else if (glu::isDataTypeUintOrUVec(spec.type))
		{
			deUint32 val[4];
			for (int i = 0; i < typeScalarSize; i++)
			{
				DE_ASSERT(spec.minValue.i[i] >= 0 && spec.maxValue.i[i] >= 0);
				val[i] = (deUint32)rnd.getInt(spec.minValue.i[i], spec.maxValue.i[i]);
			}

			switch (spec.type)
			{
				case glu::TYPE_UINT:		glUniform1ui(location, val[0]);							break;
				case glu::TYPE_UINT_VEC2:	glUniform2ui(location, val[0], val[1]);					break;
				case glu::TYPE_UINT_VEC3:	glUniform3ui(location, val[0], val[1], val[2]);			break;
				case glu::TYPE_UINT_VEC4:	glUniform4ui(location, val[0], val[1], val[2], val[3]);	break;
				default: DE_ASSERT(false);
			}
		}
		else
			DE_ASSERT(false);
	}
}

void Program::setAttribute (const Buffer& attrBuf, const int attrBufOffset, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const
{
	const int attrLoc = glGetAttribLocation(m_programGL, mangleShaderNames(attrSpec.name, shaderNameManglingSuffix).c_str());

	glEnableVertexAttribArray(attrLoc);
	attrBuf.bind(GL_ARRAY_BUFFER);

	if (glu::isDataTypeFloatOrVec(attrSpec.type))
		glVertexAttribPointer(attrLoc, glu::getDataTypeScalarSize(attrSpec.type), GL_FLOAT, GL_FALSE, 0, (GLvoid*)(deIntptr)attrBufOffset);
	else
		DE_ASSERT(false);
}

void Program::setAttributeClientMem (const void* const attrData, const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const
{
	const int attrLoc = glGetAttribLocation(m_programGL, mangleShaderNames(attrSpec.name, shaderNameManglingSuffix).c_str());

	glEnableVertexAttribArray(attrLoc);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	if (glu::isDataTypeFloatOrVec(attrSpec.type))
		glVertexAttribPointer(attrLoc, glu::getDataTypeScalarSize(attrSpec.type), GL_FLOAT, GL_FALSE, 0, attrData);
	else
		DE_ASSERT(false);
}

void Program::disableAttributeArray (const VarSpec& attrSpec, const string& shaderNameManglingSuffix) const
{
	const int attrLoc = glGetAttribLocation(m_programGL, mangleShaderNames(attrSpec.name, shaderNameManglingSuffix).c_str());

	glDisableVertexAttribArray(attrLoc);
}

/*--------------------------------------------------------------------*//*!
 * \brief Container class for managing GL objects
 *
 * GLObjectManager can be used for objects of class Program, Buffer or
 * Texture. In the manager, each such object is associated with a name that
 * is used to access it.
 *
 * In addition to the making, getting and removing functions, the manager
 * supports marking objects as "garbage", meaning they're not yet
 * destroyed, but can be later destroyed with removeRandomGarbage(). The
 * idea is that if we want to stress test with high memory usage, we can
 * continuously move objects to garbage after using them, and when a memory
 * limit is reached, we can call removeGarbageUntilUnder(limit, rnd). This
 * way we can approximately keep our memory usage at just under the wanted
 * limit.
 *
 * The manager also supports querying the approximate amount of GL memory
 * used by its objects.
 *
 * \note The memory usage related functions are not currently supported
 *		 for Program objects.
 *//*--------------------------------------------------------------------*/
template <typename T>
class GLObjectManager
{
public:
	void						make						(const string& name)								{ DE_ASSERT(!has(name)); m_objects[name] = SharedPtr<T>(new T); }
	void						make						(const string& name, gls::TextureType texType)		{ DE_ASSERT(!has(name)); m_objects[name] = SharedPtr<T>(new T(texType)); }
	bool						has							(const string& name) const	{ return m_objects.find(name) != m_objects.end(); }
	const T&					get							(const string& name) const;
	T&							get							(const string& name)		{ return const_cast<T&>(((const GLObjectManager<T>*)this)->get(name)); }
	void						remove						(const string& name)		{ const int removed = (int)m_objects.erase(name); DE_ASSERT(removed); DE_UNREF(removed); }
	int							computeApproxMemUsage		(void) const;
	void						markAsGarbage				(const string& name);
	int							removeRandomGarbage			(Random& rnd);
	void						removeGarbageUntilUnder		(int limit, Random& rnd);

private:
	static const char*			objTypeName					(void);

	map<string, SharedPtr<T> >	m_objects;
	vector<SharedPtr<T> >		m_garbageObjects;
};

template <> const char* GLObjectManager<Buffer>::objTypeName	(void) { return "buffer"; }
template <> const char* GLObjectManager<Texture>::objTypeName	(void) { return "texture"; }
template <> const char* GLObjectManager<Program>::objTypeName	(void) { return "program"; }

template <typename T>
const T& GLObjectManager<T>::get (const string& name) const
{
	const typename map<string, SharedPtr<T> >::const_iterator it = m_objects.find(name);
	DE_ASSERT(it != m_objects.end());
	return *it->second;
}

template <typename T>
int GLObjectManager<T>::computeApproxMemUsage (void) const
{
	int result = 0;

	for (typename map<string, SharedPtr<T> >::const_iterator it = m_objects.begin(); it != m_objects.end(); ++it)
		result += it->second->getApproxMemUsage();

	for (typename vector<SharedPtr<T> >::const_iterator it = m_garbageObjects.begin(); it != m_garbageObjects.end(); ++it)
		result += (*it)->getApproxMemUsage();

	return result;
}

template <typename T>
void GLObjectManager<T>::markAsGarbage (const string& name)
{
	const typename map<string, SharedPtr<T> >::iterator it = m_objects.find(name);
	DE_ASSERT(it != m_objects.end());
	m_garbageObjects.push_back(it->second);
	m_objects.erase(it);
}

template <typename T>
int GLObjectManager<T>::removeRandomGarbage (Random& rnd)
{
	if (m_garbageObjects.empty())
		return -1;

	const int removeNdx		= rnd.getInt(0, (int)m_garbageObjects.size()-1);
	const int memoryFreed	= m_garbageObjects[removeNdx]->getApproxMemUsage();
	m_garbageObjects.erase(m_garbageObjects.begin() + removeNdx);
	return memoryFreed;
}

template <typename T>
void GLObjectManager<T>::removeGarbageUntilUnder (const int limit, Random& rnd)
{
	int memUsage = computeApproxMemUsage();

	while (memUsage > limit)
	{
		const int memReleased = removeRandomGarbage(rnd);
		if (memReleased < 0)
			throw tcu::InternalError(string("") + "Given " + objTypeName() + " memory usage limit exceeded, and no unneeded " + objTypeName() + " resources available to release");
		memUsage -= memReleased;
		DE_ASSERT(memUsage == computeApproxMemUsage());
	}
}

} // LongStressCaseInternal

using namespace LongStressCaseInternal;

static int generateRandomAttribData (vector<deUint8>& attrDataBuf, int& dataSizeBytesDst, const VarSpec& attrSpec, const int numVertices, Random& rnd)
{
	const bool	isFloat			= glu::isDataTypeFloatOrVec(attrSpec.type);
	const int	numComponents	= glu::getDataTypeScalarSize(attrSpec.type);
	const int	componentSize	= (int)(isFloat ? sizeof(GLfloat) : sizeof(GLint));
	const int	offsetInBuf		= nextDivisible((int)attrDataBuf.size(), componentSize); // Round up for alignment.

	DE_STATIC_ASSERT(sizeof(GLint) == sizeof(int));
	DE_STATIC_ASSERT(sizeof(GLfloat) == sizeof(float));

	dataSizeBytesDst = numComponents*componentSize*numVertices;

	attrDataBuf.resize(offsetInBuf + dataSizeBytesDst);

	if (isFloat)
	{
		float* const data = (float*)&attrDataBuf[offsetInBuf];

		for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx++)
			for (int compNdx = 0; compNdx < numComponents; compNdx++)
				data[vtxNdx*numComponents + compNdx] = rnd.getFloat(attrSpec.minValue.f[compNdx], attrSpec.maxValue.f[compNdx]);
	}
	else
	{
		DE_ASSERT(glu::isDataTypeIntOrIVec(attrSpec.type));

		int* const data = (int*)&attrDataBuf[offsetInBuf];

		for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx++)
			for (int compNdx = 0; compNdx < numComponents; compNdx++)
				data[vtxNdx*numComponents + compNdx] = rnd.getInt(attrSpec.minValue.i[compNdx], attrSpec.maxValue.i[compNdx]);
	}

	return offsetInBuf;
}

static int generateRandomPositionAttribData (vector<deUint8>& attrDataBuf, int& dataSizeBytesDst, const VarSpec& attrSpec, const int numVertices, Random& rnd)
{
	DE_ASSERT(glu::isDataTypeFloatOrVec(attrSpec.type));

	const int numComponents = glu::getDataTypeScalarSize(attrSpec.type);
	DE_ASSERT(numComponents >= 2);
	const int offsetInBuf = generateRandomAttribData(attrDataBuf, dataSizeBytesDst, attrSpec, numVertices, rnd);

	if (numComponents > 2)
	{
		float* const data = (float*)&attrDataBuf[offsetInBuf];

		for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx++)
			data[vtxNdx*numComponents + 2] = -1.0f;

		for (int triNdx = 0; triNdx < numVertices-2; triNdx++)
		{
			float* const	vtxAComps	= &data[(triNdx+0)*numComponents];
			float* const	vtxBComps	= &data[(triNdx+1)*numComponents];
			float* const	vtxCComps	= &data[(triNdx+2)*numComponents];

			const float		triArea		= triangleArea(Vec2(vtxAComps[0], vtxAComps[1]),
													   Vec2(vtxBComps[0], vtxBComps[1]),
													   Vec2(vtxCComps[0], vtxCComps[1]));
			const float		t			= triArea / (triArea + 1.0f);
			const float		z			= (1.0f-t)*attrSpec.minValue.f[2] + t*attrSpec.maxValue.f[2];

			vtxAComps[2] = de::max(vtxAComps[2], z);
			vtxBComps[2] = de::max(vtxBComps[2], z);
			vtxCComps[2] = de::max(vtxCComps[2], z);
		}
	}

	return offsetInBuf;
}

static void generateAttribs (vector<deUint8>& attrDataBuf, vector<int>& attrDataOffsets, vector<int>& attrDataSizes, const vector<VarSpec>& attrSpecs, const string& posAttrName, const int numVertices, Random& rnd)
{
	attrDataBuf.clear();
	attrDataOffsets.clear();
	attrDataSizes.resize(attrSpecs.size());

	for (int i = 0; i < (int)attrSpecs.size(); i++)
	{
		if (attrSpecs[i].name == posAttrName)
			attrDataOffsets.push_back(generateRandomPositionAttribData(attrDataBuf, attrDataSizes[i], attrSpecs[i], numVertices, rnd));
		else
			attrDataOffsets.push_back(generateRandomAttribData(attrDataBuf, attrDataSizes[i], attrSpecs[i], numVertices, rnd));
	}
}

LongStressCase::LongStressCase (tcu::TestContext&				testCtx,
								const glu::RenderContext&		renderCtx,
								const char* const				name,
								const char* const				desc,
								const int						maxTexMemoryUsageBytes,
								const int						maxBufMemoryUsageBytes,
								const int						numDrawCallsPerIteration,
								const int						numTrianglesPerDrawCall,
								const vector<ProgramContext>&	programContexts,
								const FeatureProbabilities&		probabilities,
								const deUint32					indexBufferUsage,
								const deUint32					attrBufferUsage,
								const int						redundantBufferFactor,
								const bool						showDebugInfo)
	: tcu::TestCase					(testCtx, name, desc)
	, m_renderCtx					(renderCtx)
	, m_maxTexMemoryUsageBytes		(maxTexMemoryUsageBytes)
	, m_maxBufMemoryUsageBytes		(maxBufMemoryUsageBytes)
	, m_numDrawCallsPerIteration	(numDrawCallsPerIteration)
	, m_numTrianglesPerDrawCall		(numTrianglesPerDrawCall)
	, m_numVerticesPerDrawCall		(numTrianglesPerDrawCall+2) // \note Triangle strips are used.
	, m_programContexts				(programContexts)
	, m_probabilities				(probabilities)
	, m_indexBufferUsage			(indexBufferUsage)
	, m_attrBufferUsage				(attrBufferUsage)
	, m_redundantBufferFactor		(redundantBufferFactor)
	, m_showDebugInfo				(showDebugInfo)
	, m_numIterations				(getNumIterations(testCtx, 5))
	, m_isGLES3						(contextSupports(renderCtx.getType(), glu::ApiType::es(3,0)))
	, m_currentIteration			(0)
	, m_startTimeSeconds			((deUint64)-1)
	, m_lastLogTime					((deUint64)-1)
	, m_lastLogIteration			(0)
	, m_currentLogEntryNdx			(0)
	, m_rnd							(deStringHash(getName()) ^ testCtx.getCommandLine().getBaseSeed())
	, m_programs					(DE_NULL)
	, m_buffers						(DE_NULL)
	, m_textures					(DE_NULL)
	, m_debugInfoRenderer			(DE_NULL)
{
	DE_ASSERT(m_numVerticesPerDrawCall <= (int)std::numeric_limits<deUint16>::max()+1); // \note Vertices are referred to with 16-bit indices.
	DE_ASSERT(m_redundantBufferFactor > 0);
}

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

void LongStressCase::init (void)
{
	// Generate dummy texture data for each texture spec in m_programContexts.

	DE_ASSERT(!m_programContexts.empty());
	DE_ASSERT(m_programResources.empty());
	m_programResources.resize(m_programContexts.size());

	for (int progCtxNdx = 0; progCtxNdx < (int)m_programContexts.size(); progCtxNdx++)
	{
		const ProgramContext&	progCtx = m_programContexts[progCtxNdx];
		ProgramResources&		progRes = m_programResources[progCtxNdx];

		for (int texSpecNdx = 0; texSpecNdx < (int)progCtx.textureSpecs.size(); texSpecNdx++)
		{
			const TextureSpec&		spec	= progCtx.textureSpecs[texSpecNdx];
			const TextureFormat		format	= glu::mapGLTransferFormat(spec.format, spec.dataType);

			// If texture data with the same format has already been generated, re-use that (don't care much about contents).

			SharedPtr<TextureLevel> dummyTex;

			for (int prevProgCtxNdx = 0; prevProgCtxNdx < (int)m_programResources.size(); prevProgCtxNdx++)
			{
				const vector<SharedPtr<TextureLevel> >& prevProgCtxTextures = m_programResources[prevProgCtxNdx].dummyTextures;

				for (int texNdx = 0; texNdx < (int)prevProgCtxTextures.size(); texNdx++)
				{
					if (prevProgCtxTextures[texNdx]->getFormat() == format)
					{
						dummyTex = prevProgCtxTextures[texNdx];
						break;
					}
				}
			}

			if (!dummyTex)
				dummyTex = SharedPtr<TextureLevel>(new TextureLevel(format));

			if (dummyTex->getWidth() < spec.width || dummyTex->getHeight() < spec.height)
			{
				dummyTex->setSize(spec.width, spec.height);
				tcu::fillWithComponentGradients(dummyTex->getAccess(), spec.minValue, spec.maxValue);
			}

			progRes.dummyTextures.push_back(dummyTex);
		}
	}

	m_vertexIndices.clear();
	for (int i = 0; i < m_numVerticesPerDrawCall; i++)
		m_vertexIndices.push_back((deUint16)i);
	m_rnd.shuffle(m_vertexIndices.begin(), m_vertexIndices.end());

	DE_ASSERT(!m_programs && !m_buffers && !m_textures);
	m_programs = new GLObjectManager<Program>;
	m_buffers = new GLObjectManager<Buffer>;
	m_textures = new GLObjectManager<Texture>;

	m_currentIteration = 0;

	{
		TestLog& log = m_testCtx.getLog();

		log << TestLog::Message << "Number of iterations: "										<< (m_numIterations > 0 ? toString(m_numIterations) : "infinite")				<< TestLog::EndMessage
			<< TestLog::Message << "Number of draw calls per iteration: "						<< m_numDrawCallsPerIteration													<< TestLog::EndMessage
			<< TestLog::Message << "Number of triangles per draw call: "						<< m_numTrianglesPerDrawCall													<< TestLog::EndMessage
			<< TestLog::Message << "Using triangle strips"																														<< TestLog::EndMessage
			<< TestLog::Message << "Approximate texture memory usage limit: "					<< de::floatToString((float)m_maxTexMemoryUsageBytes / Mi, 2) << " MiB"			<< TestLog::EndMessage
			<< TestLog::Message << "Approximate buffer memory usage limit: "					<< de::floatToString((float)m_maxBufMemoryUsageBytes / Mi, 2) << " MiB"			<< TestLog::EndMessage
			<< TestLog::Message << "Default vertex attribute data buffer usage parameter: "		<< glu::getUsageName(m_attrBufferUsage)											<< TestLog::EndMessage
			<< TestLog::Message << "Default vertex index data buffer usage parameter: "			<< glu::getUsageName(m_indexBufferUsage)										<< TestLog::EndMessage

			<< TestLog::Section("ProbabilityParams", "Per-iteration probability parameters")
			<< TestLog::Message << "Program re-build: "															<< probabilityStr(m_probabilities.rebuildProgram)				<< TestLog::EndMessage
			<< TestLog::Message << "Texture re-upload: "														<< probabilityStr(m_probabilities.reuploadTexture)				<< TestLog::EndMessage
			<< TestLog::Message << "Buffer re-upload: "															<< probabilityStr(m_probabilities.reuploadBuffer)				<< TestLog::EndMessage
			<< TestLog::Message << "Use glTexImage* instead of glTexSubImage* when uploading texture: "			<< probabilityStr(m_probabilities.reuploadWithTexImage)			<< TestLog::EndMessage
			<< TestLog::Message << "Use glBufferData* instead of glBufferSubData* when uploading buffer: "		<< probabilityStr(m_probabilities.reuploadWithBufferData)		<< TestLog::EndMessage
			<< TestLog::Message << "Delete texture after using it, even if could re-use it: "					<< probabilityStr(m_probabilities.deleteTexture)				<< TestLog::EndMessage
			<< TestLog::Message << "Delete buffer after using it, even if could re-use it: "					<< probabilityStr(m_probabilities.deleteBuffer)					<< TestLog::EndMessage
			<< TestLog::Message << "Don't re-use texture, and only delete if memory limit is hit: "				<< probabilityStr(m_probabilities.wastefulTextureMemoryUsage)	<< TestLog::EndMessage
			<< TestLog::Message << "Don't re-use buffer, and only delete if memory limit is hit: "				<< probabilityStr(m_probabilities.wastefulBufferMemoryUsage)	<< TestLog::EndMessage
			<< TestLog::Message << "Use client memory (instead of GL buffers) for vertex attribute data: "		<< probabilityStr(m_probabilities.clientMemoryAttributeData)	<< TestLog::EndMessage
			<< TestLog::Message << "Use client memory (instead of GL buffers) for vertex index data: "			<< probabilityStr(m_probabilities.clientMemoryIndexData)		<< TestLog::EndMessage
			<< TestLog::Message << "Use random target parameter when uploading buffer data: "					<< probabilityStr(m_probabilities.randomBufferUploadTarget)		<< TestLog::EndMessage
			<< TestLog::Message << "Use random usage parameter when uploading buffer data: "					<< probabilityStr(m_probabilities.randomBufferUsage)			<< TestLog::EndMessage
			<< TestLog::Message << "Use glDrawArrays instead of glDrawElements: "								<< probabilityStr(m_probabilities.useDrawArrays)				<< TestLog::EndMessage
			<< TestLog::Message << "Use separate buffers for each attribute, instead of one array for all: "	<< probabilityStr(m_probabilities.separateAttributeBuffers)		<< TestLog::EndMessage
			<< TestLog::EndSection
			<< TestLog::Message << "Using " << m_programContexts.size() << " program(s)" << TestLog::EndMessage;

		bool anyProgramsFailed = false;
		for (int progCtxNdx = 0; progCtxNdx < (int)m_programContexts.size(); progCtxNdx++)
		{
			const ProgramContext& progCtx = m_programContexts[progCtxNdx];
			glu::ShaderProgram prog(m_renderCtx, glu::makeVtxFragSources(mangleShaderNames(progCtx.vertexSource, ""), mangleShaderNames(progCtx.fragmentSource, "")));
			log << TestLog::Section("ShaderProgram" + toString(progCtxNdx), "Shader program " + toString(progCtxNdx)) << prog << TestLog::EndSection;
			if (!prog.isOk())
				anyProgramsFailed = true;
		}

		if (anyProgramsFailed)
			throw tcu::TestError("One or more shader programs failed to compile");
	}

	DE_ASSERT(!m_debugInfoRenderer);
	if (m_showDebugInfo)
		m_debugInfoRenderer = new DebugInfoRenderer(m_renderCtx);
}

void LongStressCase::deinit (void)
{
	m_programResources.clear();

	delete m_programs;
	m_programs = DE_NULL;

	delete m_buffers;
	m_buffers = DE_NULL;

	delete m_textures;
	m_textures = DE_NULL;

	delete m_debugInfoRenderer;
	m_debugInfoRenderer = DE_NULL;
}

LongStressCase::IterateResult LongStressCase::iterate (void)
{
	TestLog&					log							= m_testCtx.getLog();
	const int					renderWidth					= m_renderCtx.getRenderTarget().getWidth();
	const int					renderHeight				= m_renderCtx.getRenderTarget().getHeight();
	const bool					useClientMemoryIndexData	= m_rnd.getFloat() < m_probabilities.clientMemoryIndexData;
	const bool					useDrawArrays				= m_rnd.getFloat() < m_probabilities.useDrawArrays;
	const bool					separateAttributeBuffers	= m_rnd.getFloat() < m_probabilities.separateAttributeBuffers;
	const int					progContextNdx				= m_rnd.getInt(0, (int)m_programContexts.size()-1);
	const ProgramContext&		programContext				= m_programContexts[progContextNdx];
	ProgramResources&			programResources			= m_programResources[progContextNdx];
	const string				programName					= "prog" + toString(progContextNdx);
	const string				textureNamePrefix			= "tex" + toString(progContextNdx) + "_";
	const string				unitedAttrBufferNamePrefix	= "attrBuf" + toString(progContextNdx) + "_";
	const string				indexBufferName				= "indexBuf" + toString(progContextNdx);
	const string				separateAttrBufNamePrefix	= "attrBuf" + toString(progContextNdx) + "_";

	if (m_currentIteration == 0)
		m_lastLogTime = m_startTimeSeconds = deGetTime();

	// Make or re-compile programs.
	{
		const bool hadProgram = m_programs->has(programName);

		if (!hadProgram)
			m_programs->make(programName);

		Program& prog = m_programs->get(programName);

		if (!hadProgram || m_rnd.getFloat() < m_probabilities.rebuildProgram)
		{
			programResources.shaderNameManglingSuffix = toString((deUint16)deUint64Hash((deUint64)m_currentIteration ^ deGetTime()));

			prog.setSources(mangleShaderNames(programContext.vertexSource, programResources.shaderNameManglingSuffix),
							mangleShaderNames(programContext.fragmentSource, programResources.shaderNameManglingSuffix));

			prog.build(log);
		}

		prog.use();
	}

	Program& program = m_programs->get(programName);

	// Make or re-upload textures.

	for (int texNdx = 0; texNdx < (int)programContext.textureSpecs.size(); texNdx++)
	{
		const string		texName		= textureNamePrefix + toString(texNdx);
		const bool			hadTexture	= m_textures->has(texName);
		const TextureSpec&	spec		= programContext.textureSpecs[texNdx];

		if (!hadTexture)
			m_textures->make(texName, spec.textureType);

		if (!hadTexture || m_rnd.getFloat() < m_probabilities.reuploadTexture)
		{
			Texture& texture = m_textures->get(texName);

			m_textures->removeGarbageUntilUnder(m_maxTexMemoryUsageBytes - texture.getApproxMemUsageDiff(spec.width, spec.height, spec.internalFormat, spec.useMipmap), m_rnd);

			if (!hadTexture || m_rnd.getFloat() < m_probabilities.reuploadWithTexImage)
				texture.setData(programResources.dummyTextures[texNdx]->getAccess(), spec.width, spec.height, spec.internalFormat, spec.useMipmap);
			else
				texture.setSubData(programResources.dummyTextures[texNdx]->getAccess(), 0, 0, spec.width, spec.height);

			texture.toUnit(0);
			texture.setWrap(spec.sWrap, spec.tWrap);
			texture.setFilter(spec.minFilter, spec.magFilter);
		}
	}

	// Bind textures to units, in random order (because when multiple texture specs have same unit, we want to pick one randomly).

	{
		vector<int> texSpecIndices(programContext.textureSpecs.size());
		for (int i = 0; i < (int)texSpecIndices.size(); i++)
			texSpecIndices[i] = i;
		m_rnd.shuffle(texSpecIndices.begin(), texSpecIndices.end());
		for (int i = 0; i < (int)texSpecIndices.size(); i++)
			m_textures->get(textureNamePrefix + toString(texSpecIndices[i])).toUnit(programContext.textureSpecs[i].textureUnit);
	}

	// Make or re-upload index buffer.

	if (!useDrawArrays)
	{
		m_rnd.shuffle(m_vertexIndices.begin(), m_vertexIndices.end());

		if (!useClientMemoryIndexData)
		{
			const bool hadIndexBuffer = m_buffers->has(indexBufferName);

			if (!hadIndexBuffer)
				m_buffers->make(indexBufferName);

			Buffer& indexBuf = m_buffers->get(indexBufferName);

			if (!hadIndexBuffer || m_rnd.getFloat() < m_probabilities.reuploadBuffer)
			{
				m_buffers->removeGarbageUntilUnder(m_maxBufMemoryUsageBytes - indexBuf.getApproxMemUsageDiff(m_vertexIndices), m_rnd);
				const deUint32 target = m_rnd.getFloat() < m_probabilities.randomBufferUploadTarget ? randomBufferTarget(m_rnd, m_isGLES3) : GL_ELEMENT_ARRAY_BUFFER;

				if (!hadIndexBuffer || m_rnd.getFloat() < m_probabilities.reuploadWithBufferData)
					indexBuf.setData(m_vertexIndices, target, m_rnd.getFloat() < m_probabilities.randomBufferUsage ? randomBufferUsage(m_rnd, m_isGLES3) : m_indexBufferUsage);
				else
					indexBuf.setSubData(m_vertexIndices, 0, m_numVerticesPerDrawCall, target);
			}
		}
	}

	// Set vertex attributes. If not using client-memory data, make or re-upload attribute buffers.

	generateAttribs(programResources.attrDataBuf, programResources.attrDataOffsets, programResources.attrDataSizes,
					programContext.attributes, programContext.positionAttrName, m_numVerticesPerDrawCall, m_rnd);

	if (!(m_rnd.getFloat() < m_probabilities.clientMemoryAttributeData))
	{
		if (separateAttributeBuffers)
		{
			for (int attrNdx = 0; attrNdx < (int)programContext.attributes.size(); attrNdx++)
			{
				const int usedRedundantBufferNdx = m_rnd.getInt(0, m_redundantBufferFactor-1);

				for (int redundantBufferNdx = 0; redundantBufferNdx < m_redundantBufferFactor; redundantBufferNdx++)
				{
					const string	curAttrBufName		= separateAttrBufNamePrefix + toString(attrNdx) + "_" + toString(redundantBufferNdx);
					const bool		hadCurAttrBuffer	= m_buffers->has(curAttrBufName);

					if (!hadCurAttrBuffer)
						m_buffers->make(curAttrBufName);

					Buffer& curAttrBuf = m_buffers->get(curAttrBufName);

					if (!hadCurAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadBuffer)
					{
						m_buffers->removeGarbageUntilUnder(m_maxBufMemoryUsageBytes - curAttrBuf.getApproxMemUsageDiff(programResources.attrDataSizes[attrNdx]), m_rnd);
						const deUint32 target = m_rnd.getFloat() < m_probabilities.randomBufferUploadTarget ? randomBufferTarget(m_rnd, m_isGLES3) : GL_ARRAY_BUFFER;

						if (!hadCurAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadWithBufferData)
							curAttrBuf.setData(&programResources.attrDataBuf[programResources.attrDataOffsets[attrNdx]], programResources.attrDataSizes[attrNdx], target,
											   m_rnd.getFloat() < m_probabilities.randomBufferUsage ? randomBufferUsage(m_rnd, m_isGLES3) : m_attrBufferUsage);
						else
							curAttrBuf.setSubData(&programResources.attrDataBuf[programResources.attrDataOffsets[attrNdx]], 0, programResources.attrDataSizes[attrNdx], target);
					}

					if (redundantBufferNdx == usedRedundantBufferNdx)
						program.setAttribute(curAttrBuf, 0, programContext.attributes[attrNdx], programResources.shaderNameManglingSuffix);
				}
			}
		}
		else
		{
			const int usedRedundantBufferNdx = m_rnd.getInt(0, m_redundantBufferFactor-1);

			for (int redundantBufferNdx = 0; redundantBufferNdx < m_redundantBufferFactor; redundantBufferNdx++)
			{
				const string	attrBufName		= unitedAttrBufferNamePrefix + toString(redundantBufferNdx);
				const bool		hadAttrBuffer	= m_buffers->has(attrBufName);

				if (!hadAttrBuffer)
					m_buffers->make(attrBufName);

				Buffer& attrBuf = m_buffers->get(attrBufName);

				if (!hadAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadBuffer)
				{
					m_buffers->removeGarbageUntilUnder(m_maxBufMemoryUsageBytes - attrBuf.getApproxMemUsageDiff(programResources.attrDataBuf), m_rnd);
					const deUint32 target = m_rnd.getFloat() < m_probabilities.randomBufferUploadTarget ? randomBufferTarget(m_rnd, m_isGLES3) : GL_ARRAY_BUFFER;

					if (!hadAttrBuffer || m_rnd.getFloat() < m_probabilities.reuploadWithBufferData)
						attrBuf.setData(programResources.attrDataBuf, target, m_rnd.getFloat() < m_probabilities.randomBufferUsage ? randomBufferUsage(m_rnd, m_isGLES3) : m_attrBufferUsage);
					else
						attrBuf.setSubData(programResources.attrDataBuf, 0, (int)programResources.attrDataBuf.size(), target);
				}

				if (redundantBufferNdx == usedRedundantBufferNdx)
				{
					for (int i = 0; i < (int)programContext.attributes.size(); i++)
						program.setAttribute(attrBuf, programResources.attrDataOffsets[i], programContext.attributes[i], programResources.shaderNameManglingSuffix);
				}
			}
		}
	}
	else
	{
		for (int i = 0; i < (int)programContext.attributes.size(); i++)
			program.setAttributeClientMem(&programResources.attrDataBuf[programResources.attrDataOffsets[i]], programContext.attributes[i], programResources.shaderNameManglingSuffix);
	}

	// Draw.

	glViewport(0, 0, renderWidth, renderHeight);

	glClearDepthf(1.0f);
	glClear(GL_DEPTH_BUFFER_BIT);
	glEnable(GL_DEPTH_TEST);

	for (int i = 0; i < m_numDrawCallsPerIteration; i++)
	{
		program.use();
		program.setRandomUniforms(programContext.uniforms, programResources.shaderNameManglingSuffix, m_rnd);

		if (useDrawArrays)
			glDrawArrays(GL_TRIANGLE_STRIP, 0, m_numVerticesPerDrawCall);
		else
		{
			if (useClientMemoryIndexData)
			{
				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
				glDrawElements(GL_TRIANGLE_STRIP, m_numVerticesPerDrawCall, GL_UNSIGNED_SHORT, &m_vertexIndices[0]);
			}
			else
			{
				m_buffers->get(indexBufferName).bind(GL_ELEMENT_ARRAY_BUFFER);
				glDrawElements(GL_TRIANGLE_STRIP, m_numVerticesPerDrawCall, GL_UNSIGNED_SHORT, DE_NULL);
			}
		}
	}

	for(int i = 0; i < (int)programContext.attributes.size(); i++)
		program.disableAttributeArray(programContext.attributes[i], programResources.shaderNameManglingSuffix);

	if (m_showDebugInfo)
		m_debugInfoRenderer->drawInfo(deGetTime()-m_startTimeSeconds, m_textures->computeApproxMemUsage(), m_maxTexMemoryUsageBytes, m_buffers->computeApproxMemUsage(), m_maxBufMemoryUsageBytes, m_currentIteration);

	if (m_currentIteration > 0)
	{
		// Log if a certain amount of time has passed since last log entry (or if this is the last iteration).

		const deUint64	loggingIntervalSeconds	= 10;
		const deUint64	time					= deGetTime();
		const deUint64	timeDiff				= time - m_lastLogTime;
		const int		iterDiff				= m_currentIteration - m_lastLogIteration;

		if (timeDiff >= loggingIntervalSeconds || m_currentIteration == m_numIterations-1)
		{
			log << TestLog::Section("LogEntry" + toString(m_currentLogEntryNdx), "Log entry " + toString(m_currentLogEntryNdx))
				<< TestLog::Message << "Time elapsed: " << getTimeStr(time - m_startTimeSeconds) << TestLog::EndMessage
				<< TestLog::Message << "Frame number: " << m_currentIteration << TestLog::EndMessage
				<< TestLog::Message << "Time since last log entry: " << timeDiff << "s" << TestLog::EndMessage
				<< TestLog::Message << "Frames since last log entry: " << iterDiff << TestLog::EndMessage
				<< TestLog::Message << "Average frame time since last log entry: " << de::floatToString((float)timeDiff / iterDiff, 2) << "s" << TestLog::EndMessage
				<< TestLog::Message << "Approximate texture memory usage: "
									<< de::floatToString((float)m_textures->computeApproxMemUsage() / Mi, 2) << " MiB / "
									<< de::floatToString((float)m_maxTexMemoryUsageBytes / Mi, 2) << " MiB"
									<< TestLog::EndMessage
				<< TestLog::Message << "Approximate buffer memory usage: "
										<< de::floatToString((float)m_buffers->computeApproxMemUsage() / Mi, 2) << " MiB / "
										<< de::floatToString((float)m_maxBufMemoryUsageBytes / Mi, 2) << " MiB"
										<< TestLog::EndMessage
				<< TestLog::EndSection;

			m_lastLogTime		= time;
			m_lastLogIteration	= m_currentIteration;
			m_currentLogEntryNdx++;
		}
	}

	// Possibly remove or set-as-garbage some objects, depending on given probabilities.

	for (int texNdx = 0; texNdx < (int)programContext.textureSpecs.size(); texNdx++)
	{
		const string texName = textureNamePrefix + toString(texNdx);
		if (m_rnd.getFloat() < m_probabilities.deleteTexture)
			m_textures->remove(texName);
		else if (m_rnd.getFloat() < m_probabilities.wastefulTextureMemoryUsage)
			m_textures->markAsGarbage(texName);

	}

	if (m_buffers->has(indexBufferName))
	{
		if (m_rnd.getFloat() < m_probabilities.deleteBuffer)
			m_buffers->remove(indexBufferName);
		else if (m_rnd.getFloat() < m_probabilities.wastefulBufferMemoryUsage)
			m_buffers->markAsGarbage(indexBufferName);

	}

	if (separateAttributeBuffers)
	{
		for (int attrNdx = 0; attrNdx < (int)programContext.attributes.size(); attrNdx++)
		{
			const string curAttrBufNamePrefix = separateAttrBufNamePrefix + toString(attrNdx) + "_";

			if (m_buffers->has(curAttrBufNamePrefix + "0"))
			{
				if (m_rnd.getFloat() < m_probabilities.deleteBuffer)
				{
					for (int i = 0; i < m_redundantBufferFactor; i++)
						m_buffers->remove(curAttrBufNamePrefix + toString(i));
				}
				else if (m_rnd.getFloat() < m_probabilities.wastefulBufferMemoryUsage)
				{
					for (int i = 0; i < m_redundantBufferFactor; i++)
						m_buffers->markAsGarbage(curAttrBufNamePrefix + toString(i));
				}
			}
		}
	}
	else
	{
		if (m_buffers->has(unitedAttrBufferNamePrefix + "0"))
		{
			if (m_rnd.getFloat() < m_probabilities.deleteBuffer)
			{
				for (int i = 0; i < m_redundantBufferFactor; i++)
					m_buffers->remove(unitedAttrBufferNamePrefix + toString(i));
			}
			else if (m_rnd.getFloat() < m_probabilities.wastefulBufferMemoryUsage)
			{
				for (int i = 0; i < m_redundantBufferFactor; i++)
					m_buffers->markAsGarbage(unitedAttrBufferNamePrefix + toString(i));
			}
		}
	}

	GLU_CHECK_MSG("End of LongStressCase::iterate()");

	m_currentIteration++;
	if (m_currentIteration == m_numIterations)
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Passed");
		return STOP;
	}
	else
		return CONTINUE;
}

} // gls
} // deqp