/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2015 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 Primitive bounding box tests.
 *//*--------------------------------------------------------------------*/

#include "es31fPrimitiveBoundingBoxTests.hpp"

#include "tcuTestLog.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluContextInfo.hpp"
#include "gluRenderContext.hpp"
#include "gluStrUtil.hpp"
#include "gluShaderProgram.hpp"
#include "gluObjectWrapper.hpp"
#include "gluPixelTransfer.hpp"
#include "glsStateQueryUtil.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deRandom.hpp"
#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"

#include <vector>
#include <sstream>
#include <algorithm>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

namespace StateQueryUtil = ::deqp::gls::StateQueryUtil;

struct BoundingBox
{
	tcu::Vec4 min;
	tcu::Vec4 max;

	/*--------------------------------------------------------------------*//*!
	 * Get component by index of a 8-component vector constructed by
	 * concatenating 4-component min and max vectors.
	 *//*--------------------------------------------------------------------*/
	float&			getComponentAccess	(int ndx);
	const float&	getComponentAccess	(int ndx) const;
};

float& BoundingBox::getComponentAccess (int ndx)
{
	DE_ASSERT(ndx >= 0 && ndx < 8);
	if (ndx < 4)
		return min[ndx];
	else
		return max[ndx-4];
}

const float& BoundingBox::getComponentAccess (int ndx) const
{
	return const_cast<BoundingBox*>(this)->getComponentAccess(ndx);
}

struct ProjectedBBox
{
	tcu::Vec3	min;
	tcu::Vec3	max;
};

static ProjectedBBox projectBoundingBox (const BoundingBox& bbox)
{
	const float		wMin	= de::max(0.0f, bbox.min.w()); // clamp to w=0 as extension requires
	const float		wMax	= de::max(0.0f, bbox.max.w());
	ProjectedBBox	retVal;

	retVal.min = tcu::min(bbox.min.swizzle(0, 1, 2) / wMin,
						  bbox.min.swizzle(0, 1, 2) / wMax);
	retVal.max = tcu::max(bbox.max.swizzle(0, 1, 2) / wMin,
						  bbox.max.swizzle(0, 1, 2) / wMax);
	return retVal;
}

static tcu::IVec4 getViewportBoundingBoxArea (const ProjectedBBox& bbox, const tcu::IVec2& viewportSize, float size = 0.0f)
{
	tcu::Vec4	vertexBox;
	tcu::IVec4	pixelBox;

	vertexBox.x() = (bbox.min.x() * 0.5f + 0.5f) * (float)viewportSize.x();
	vertexBox.y() = (bbox.min.y() * 0.5f + 0.5f) * (float)viewportSize.y();
	vertexBox.z() = (bbox.max.x() * 0.5f + 0.5f) * (float)viewportSize.x();
	vertexBox.w() = (bbox.max.y() * 0.5f + 0.5f) * (float)viewportSize.y();

	pixelBox.x() = deFloorFloatToInt32(vertexBox.x() - size/2.0f);
	pixelBox.y() = deFloorFloatToInt32(vertexBox.y() - size/2.0f);
	pixelBox.z() = deCeilFloatToInt32(vertexBox.z() + size/2.0f);
	pixelBox.w() = deCeilFloatToInt32(vertexBox.w() + size/2.0f);
	return pixelBox;
}


class InitialValueCase : public TestCase
{
public:
					InitialValueCase	(Context& context, const char* name, const char* desc);

	void			init				(void);
	IterateResult	iterate				(void);
};

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

void InitialValueCase::init (void)
{
	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_primitive_bounding_box"))
		throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");
}

InitialValueCase::IterateResult InitialValueCase::iterate (void)
{
	StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLfloat[8]>	state;
	glu::CallLogWrapper											gl		(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

	gl.enableLogging(true);

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Querying GL_PRIMITIVE_BOUNDING_BOX_EXT, expecting (-1, -1, -1, 1) (1, 1, 1, 1)"
		<< tcu::TestLog::EndMessage;

	gl.glGetFloatv(GL_PRIMITIVE_BOUNDING_BOX_EXT, state);
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "query");

	if (!state.verifyValidity(m_testCtx))
		return STOP;

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Got " << tcu::formatArray(&state[0], &state[8])
		<< tcu::TestLog::EndMessage;

	if ((state[0] != -1.0f) || (state[1] != -1.0f) || (state[2] != -1.0f) || (state[3] != 1.0f) ||
		(state[4] !=  1.0f) || (state[5] !=  1.0f) || (state[6] !=  1.0f) || (state[7] != 1.0f))
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Error, unexpected value"
			<< tcu::TestLog::EndMessage;

		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid initial value");
	}
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	return STOP;
}

class QueryCase : public TestCase
{
public:
	enum QueryMethod
	{
		QUERY_FLOAT = 0,
		QUERY_BOOLEAN,
		QUERY_INT,
		QUERY_INT64,

		QUERY_LAST
	};

						QueryCase	(Context& context, const char* name, const char* desc, QueryMethod method);

private:
	void				init		(void);
	IterateResult		iterate		(void);

	bool				verifyState	(glu::CallLogWrapper& gl, const BoundingBox& bbox) const;

	const QueryMethod	m_method;
};

QueryCase::QueryCase (Context& context, const char* name, const char* desc, QueryMethod method)
	: TestCase	(context, name, desc)
	, m_method	(method)
{
	DE_ASSERT(method < QUERY_LAST);
}

void QueryCase::init (void)
{
	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_primitive_bounding_box"))
		throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");
}

QueryCase::IterateResult QueryCase::iterate (void)
{
	static const BoundingBox fixedCases[] =
	{
		{ tcu::Vec4( 0.0f,  0.0f,  0.0f,  0.0f), tcu::Vec4( 0.0f,  0.0f,  0.0f,  0.0f) },
		{ tcu::Vec4(-0.0f, -0.0f, -0.0f, -0.0f), tcu::Vec4( 0.0f,  0.0f,  0.0f, -0.0f) },
		{ tcu::Vec4( 0.0f,  0.0f,  0.0f,  0.0f), tcu::Vec4( 1.0f,  1.0f,  1.0f, -1.0f) },
		{ tcu::Vec4( 2.0f,  2.0f,  2.0f,  2.0f), tcu::Vec4( 1.5f,  1.5f,  1.5f,  1.0f) },
		{ tcu::Vec4( 1.0f,  1.0f,  1.0f,  1.0f), tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.0f) },
		{ tcu::Vec4( 1.0f,  1.0f,  1.0f,  0.3f), tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.2f) },
	};

	const int					numRandomCases	= 9;
	glu::CallLogWrapper			gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	de::Random					rnd				(0xDE3210);
	std::vector<BoundingBox>	cases;

	cases.insert(cases.begin(), DE_ARRAY_BEGIN(fixedCases), DE_ARRAY_END(fixedCases));
	for (int ndx = 0; ndx < numRandomCases; ++ndx)
	{
		BoundingBox	boundingBox;

		// parameter evaluation order is not guaranteed, cannot just do "max = (rand(), rand(), ...)
		for (int coordNdx = 0; coordNdx < 8; ++coordNdx)
			boundingBox.getComponentAccess(coordNdx) = rnd.getFloat(-4.0f, 4.0f);

		cases.push_back(boundingBox);
	}

	gl.enableLogging(true);
	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	for (int caseNdx = 0; caseNdx < (int)cases.size(); ++caseNdx)
	{
		const tcu::ScopedLogSection	section		(m_testCtx.getLog(), "Iteration", "Iteration " + de::toString(caseNdx+1));
		const BoundingBox&			boundingBox	= cases[caseNdx];

		gl.glPrimitiveBoundingBox(boundingBox.min.x(), boundingBox.min.y(), boundingBox.min.z(), boundingBox.min.w(),
								  boundingBox.max.x(), boundingBox.max.y(), boundingBox.max.z(), boundingBox.max.w());

		if (!verifyState(gl, boundingBox))
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected query result");
	}

	return STOP;
}

bool QueryCase::verifyState (glu::CallLogWrapper& gl, const BoundingBox& bbox) const
{
	switch (m_method)
	{
		case QUERY_FLOAT:
		{
			StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLfloat[8]>	state;
			bool														error = false;

			gl.glGetFloatv(GL_PRIMITIVE_BOUNDING_BOX_EXT, state);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "query");

			if (!state.verifyValidity(m_testCtx))
				return false;

			m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "glGetFloatv returned " << tcu::formatArray(&state[0], &state[8])
					<< tcu::TestLog::EndMessage;

			for (int ndx = 0; ndx < 8; ++ndx)
				if (state[ndx] != bbox.getComponentAccess(ndx))
					error = true;

			if (error)
			{
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "Error, unexpected value\n"
					<< "Expected ["
					<< bbox.min.x() << ", " << bbox.min.y() << ", " << bbox.min.z() << ", " << bbox.min.w() << ", "
					<< bbox.max.x() << ", " << bbox.max.y() << ", " << bbox.max.z() << ", " << bbox.max.w() << "]"
					<< tcu::TestLog::EndMessage;
				return false;
			}
			return true;
		}

		case QUERY_INT:
		{
			StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint[8]>	state;
			bool														error = false;

			gl.glGetIntegerv(GL_PRIMITIVE_BOUNDING_BOX_EXT, state);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "query");

			if (!state.verifyValidity(m_testCtx))
				return false;

			m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "glGetIntegerv returned " << tcu::formatArray(&state[0], &state[8])
					<< tcu::TestLog::EndMessage;

			for (int ndx = 0; ndx < 8; ++ndx)
				if (state[ndx] != StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<glw::GLint>(bbox.getComponentAccess(ndx)) &&
					state[ndx] != StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<glw::GLint>(bbox.getComponentAccess(ndx)))
					error = true;

			if (error)
			{
				tcu::MessageBuilder builder(&m_testCtx.getLog());

				builder	<< "Error, unexpected value\n"
						<< "Expected [";

				for (int ndx = 0; ndx < 8; ++ndx)
				{
					const glw::GLint roundDown	= StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<glw::GLint>(bbox.getComponentAccess(ndx));
					const glw::GLint roundUp	= StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<glw::GLint>(bbox.getComponentAccess(ndx));

					if (ndx != 0)
						builder << ", ";

					if (roundDown == roundUp)
						builder << roundDown;
					else
						builder << "{" << roundDown << ", " << roundUp << "}";
				}

				builder	<< "]"
						<< tcu::TestLog::EndMessage;
				return false;
			}
			return true;
		}

		case QUERY_INT64:
		{
			StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint64[8]>	state;
			bool																error = false;

			gl.glGetInteger64v(GL_PRIMITIVE_BOUNDING_BOX_EXT, state);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "query");

			if (!state.verifyValidity(m_testCtx))
				return false;

			m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "glGetInteger64v returned " << tcu::formatArray(&state[0], &state[8])
					<< tcu::TestLog::EndMessage;

			for (int ndx = 0; ndx < 8; ++ndx)
				if (state[ndx] != StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<glw::GLint64>(bbox.getComponentAccess(ndx)) &&
					state[ndx] != StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<glw::GLint64>(bbox.getComponentAccess(ndx)))
					error = true;

			if (error)
			{
				tcu::MessageBuilder builder(&m_testCtx.getLog());

				builder	<< "Error, unexpected value\n"
						<< "Expected [";

				for (int ndx = 0; ndx < 8; ++ndx)
				{
					const glw::GLint64 roundDown	= StateQueryUtil::roundGLfloatToNearestIntegerHalfDown<glw::GLint64>(bbox.getComponentAccess(ndx));
					const glw::GLint64 roundUp		= StateQueryUtil::roundGLfloatToNearestIntegerHalfUp<glw::GLint64>(bbox.getComponentAccess(ndx));

					if (ndx != 0)
						builder << ", ";

					if (roundDown == roundUp)
						builder << roundDown;
					else
						builder << "{" << roundDown << ", " << roundUp << "}";
				}

				builder	<< "]"
						<< tcu::TestLog::EndMessage;
				return false;
			}
			return true;
		}

		case QUERY_BOOLEAN:
		{
			StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLboolean[8]>	state;
			bool															error = false;

			gl.glGetBooleanv(GL_PRIMITIVE_BOUNDING_BOX_EXT, state);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "query");

			if (!state.verifyValidity(m_testCtx))
				return false;

			m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "glGetBooleanv returned ["
					<< glu::getBooleanStr(state[0]) << ", " << glu::getBooleanStr(state[1]) << ", " << glu::getBooleanStr(state[2]) << ", " << glu::getBooleanStr(state[3]) << ", "
					<< glu::getBooleanStr(state[4]) << ", " << glu::getBooleanStr(state[5]) << ", " << glu::getBooleanStr(state[6]) << ", " << glu::getBooleanStr(state[7]) << "]\n"
					<< tcu::TestLog::EndMessage;

			for (int ndx = 0; ndx < 8; ++ndx)
				if (state[ndx] != ((bbox.getComponentAccess(ndx) != 0.0f) ? (GL_TRUE) : (GL_FALSE)))
					error = true;

			if (error)
			{
				tcu::MessageBuilder builder(&m_testCtx.getLog());

				builder	<< "Error, unexpected value\n"
						<< "Expected [";

				for (int ndx = 0; ndx < 8; ++ndx)
				{
					if (ndx != 0)
						builder << ", ";

					builder << ((bbox.getComponentAccess(ndx) != 0.0f) ? ("GL_TRUE") : ("GL_FALSE"));
				}

				builder	<< "]"
						<< tcu::TestLog::EndMessage;
				return false;
			}
			return true;
		}

		default:
			DE_ASSERT(false);
			return true;
	}
}

class BBoxRenderCase : public TestCase
{
public:
	enum
	{
		FLAG_RENDERTARGET_DEFAULT	= 1u << 0, //!< render to default renderbuffer
		FLAG_RENDERTARGET_FBO		= 1u << 1, //!< render to framebuffer object

		FLAG_BBOXSIZE_EQUAL			= 1u << 2, //!< set tight primitive bounding box
		FLAG_BBOXSIZE_LARGER		= 1u << 3, //!< set padded primitive bounding box
		FLAG_BBOXSIZE_SMALLER		= 1u << 4, //!< set too small primitive bounding box

		FLAG_TESSELLATION			= 1u << 5, //!< use tessellation shader
		FLAG_GEOMETRY				= 1u << 6, //!< use geometry shader

		FLAG_SET_BBOX_STATE			= 1u << 7, //!< set primitive bounding box using global state
		FLAG_SET_BBOX_OUTPUT		= 1u << 8, //!< set primitive bounding box using tessellation output
		FLAG_PER_PRIMITIVE_BBOX		= 1u << 9, //!< set primitive bounding per primitive

		FLAGBIT_USER_BIT			= 10u //!< bits N and and up are reserved for subclasses
	};

									BBoxRenderCase					(Context& context, const char* name, const char* description, int numIterations, deUint32 flags);
									~BBoxRenderCase					(void);

protected:
	enum RenderTarget
	{
		RENDERTARGET_DEFAULT,
		RENDERTARGET_FBO,
	};
	enum BBoxSize
	{
		BBOXSIZE_EQUAL,
		BBOXSIZE_LARGER,
		BBOXSIZE_SMALLER,
	};

	enum
	{
		RENDER_TARGET_MIN_SIZE	= 256,
		FBO_SIZE				= 512,
		MIN_VIEWPORT_SIZE		= 256,
		MAX_VIEWPORT_SIZE		= 512,
	};
	DE_STATIC_ASSERT(MIN_VIEWPORT_SIZE <= RENDER_TARGET_MIN_SIZE);

	enum
	{
		VA_POS_VEC_NDX		= 0,
		VA_COL_VEC_NDX		= 1,
		VA_NUM_ATTRIB_VECS	= 2,
	};

	enum AABBRoundDirection
	{
		ROUND_INWARDS = 0,
		ROUND_OUTWARDS
	};

	struct IterationConfig
	{
		tcu::IVec2	viewportPos;
		tcu::IVec2	viewportSize;
		tcu::Vec2	patternPos;		//!< in NDC
		tcu::Vec2	patternSize;	//!< in NDC
		BoundingBox	bbox;
	};

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

	virtual std::string				genVertexSource					(void) const = 0;
	virtual std::string				genFragmentSource				(void) const = 0;
	virtual std::string				genTessellationControlSource	(void) const = 0;
	virtual std::string				genTessellationEvaluationSource	(void) const = 0;
	virtual std::string				genGeometrySource				(void) const = 0;

	virtual IterationConfig			generateConfig					(int iteration, const tcu::IVec2& renderTargetSize) const = 0;
	virtual void					getAttributeData				(std::vector<tcu::Vec4>& data) const = 0;
	virtual void					renderTestPattern				(const IterationConfig& config) = 0;
	virtual void					verifyRenderResult				(const IterationConfig& config) = 0;

	IterationConfig					generateRandomConfig			(int seed, const tcu::IVec2& renderTargetSize) const;
	tcu::IVec4						getViewportPatternArea			(const tcu::Vec2& patternPos, const tcu::Vec2& patternSize, const tcu::IVec2& viewportSize, AABBRoundDirection roundDir) const;

	void							setupRender						(const IterationConfig& config);

	enum ShaderFunction
	{
		SHADER_FUNC_MIRROR_X,
		SHADER_FUNC_MIRROR_Y,
		SHADER_FUNC_INSIDE_BBOX,
	};

	const char*						genShaderFunction				(ShaderFunction func) const;

	const RenderTarget				m_renderTarget;
	const BBoxSize					m_bboxSize;
	const bool						m_hasTessellationStage;
	const bool						m_hasGeometryStage;
	const bool						m_useGlobalState;
	const bool						m_calcPerPrimitiveBBox;
	const int						m_numIterations;

	de::MovePtr<glu::ShaderProgram>	m_program;
	de::MovePtr<glu::Buffer>		m_vbo;
	de::MovePtr<glu::Framebuffer>	m_fbo;

private:
	std::vector<IterationConfig>	m_iterationConfigs;
	int								m_iteration;
};

BBoxRenderCase::BBoxRenderCase (Context& context, const char* name, const char* description, int numIterations, deUint32 flags)
	: TestCase					(context, name, description)
	, m_renderTarget			((flags & FLAG_RENDERTARGET_DEFAULT) ? (RENDERTARGET_DEFAULT) : (RENDERTARGET_FBO))
	, m_bboxSize				((flags & FLAG_BBOXSIZE_EQUAL) ? (BBOXSIZE_EQUAL) : (flags & FLAG_BBOXSIZE_SMALLER) ? (BBOXSIZE_SMALLER) : (BBOXSIZE_LARGER))
	, m_hasTessellationStage	((flags & FLAG_TESSELLATION) != 0)
	, m_hasGeometryStage		((flags & FLAG_GEOMETRY) != 0)
	, m_useGlobalState			((flags & FLAG_SET_BBOX_STATE) != 0)
	, m_calcPerPrimitiveBBox	((flags & FLAG_PER_PRIMITIVE_BBOX) != 0)
	, m_numIterations			(numIterations)
	, m_iteration				(0)
{
	// validate flags
	DE_ASSERT((((m_renderTarget == RENDERTARGET_DEFAULT)	?	(FLAG_RENDERTARGET_DEFAULT)	: (0)) |
			   ((m_renderTarget == RENDERTARGET_FBO)		?	(FLAG_RENDERTARGET_FBO)		: (0)) |
			   ((m_bboxSize == BBOXSIZE_EQUAL)				?	(FLAG_BBOXSIZE_EQUAL)		: (0)) |
			   ((m_bboxSize == BBOXSIZE_LARGER)				?	(FLAG_BBOXSIZE_LARGER)		: (0)) |
			   ((m_bboxSize == BBOXSIZE_SMALLER)			?	(FLAG_BBOXSIZE_SMALLER)		: (0)) |
			   ((m_hasTessellationStage)					?	(FLAG_TESSELLATION)			: (0)) |
			   ((m_hasGeometryStage)						?	(FLAG_GEOMETRY)				: (0)) |
			   ((m_useGlobalState)							?	(FLAG_SET_BBOX_STATE)		: (0)) |
			   ((!m_useGlobalState)							?	(FLAG_SET_BBOX_OUTPUT)		: (0)) |
			   ((m_calcPerPrimitiveBBox)					?	(FLAG_PER_PRIMITIVE_BBOX)	: (0))) == (flags & ((1u << FLAGBIT_USER_BIT) - 1)));

	DE_ASSERT(m_useGlobalState || m_hasTessellationStage); // using non-global state requires tessellation

	if (m_calcPerPrimitiveBBox)
	{
		DE_ASSERT(!m_useGlobalState); // per-primitive test requires per-primitive (non-global) state
		DE_ASSERT(m_bboxSize == BBOXSIZE_EQUAL); // smaller is hard to verify, larger not interesting
	}
}

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

void BBoxRenderCase::init (void)
{
	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	const tcu::IVec2		renderTargetSize	= (m_renderTarget == RENDERTARGET_DEFAULT) ?
													(tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight())) :
													(tcu::IVec2(FBO_SIZE, FBO_SIZE));

	// requirements
	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_primitive_bounding_box"))
		throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");
	if (m_hasTessellationStage && !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
		throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");
	if (m_hasGeometryStage && !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
		throw tcu::NotSupportedError("Test requires GL_EXT_geometry_shader extension");
	if (m_renderTarget == RENDERTARGET_DEFAULT && (renderTargetSize.x() < RENDER_TARGET_MIN_SIZE || renderTargetSize.y() < RENDER_TARGET_MIN_SIZE))
		throw tcu::NotSupportedError(std::string() + "Test requires " + de::toString<int>(RENDER_TARGET_MIN_SIZE) + "x" + de::toString<int>(RENDER_TARGET_MIN_SIZE) + " default framebuffer");

	// log case specifics
	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Setting primitive bounding box "
			<< ((m_calcPerPrimitiveBBox)         ? ("to exactly cover each generated primitive")
			  : (m_bboxSize == BBOXSIZE_EQUAL)   ? ("to exactly cover rendered grid")
			  : (m_bboxSize == BBOXSIZE_LARGER)  ? ("to cover the grid and include some padding")
			  : (m_bboxSize == BBOXSIZE_SMALLER) ? ("to cover only a subset of the grid")
			  : (DE_NULL))
			<< ".\n"
		<< "Rendering with vertex"
			<< ((m_hasTessellationStage) ? ("-tessellation{ctrl,eval}") : (""))
			<< ((m_hasGeometryStage) ? ("-geometry") : (""))
			<< "-fragment program.\n"
		<< "Set bounding box using "
			<< ((m_useGlobalState) ? ("PRIMITIVE_BOUNDING_BOX_EXT state") : ("gl_BoundingBoxEXT output"))
			<< "\n"
		<< "Verifying rendering results are valid within the bounding box."
		<< tcu::TestLog::EndMessage;

	// resources

	{
		glu::ProgramSources sources;
		sources << glu::VertexSource(genVertexSource());
		sources << glu::FragmentSource(genFragmentSource());

		if (m_hasTessellationStage)
			sources << glu::TessellationControlSource(genTessellationControlSource())
					<< glu::TessellationEvaluationSource(genTessellationEvaluationSource());
		if (m_hasGeometryStage)
			sources << glu::GeometrySource(genGeometrySource());

		m_program = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(), sources));
		GLU_EXPECT_NO_ERROR(gl.getError(), "build program");

		{
			const tcu::ScopedLogSection section(m_testCtx.getLog(), "ShaderProgram", "Shader program");
			m_testCtx.getLog() << *m_program;
		}

		if (!m_program->isOk())
			throw tcu::TestError("failed to build program");
	}

	if (m_renderTarget == RENDERTARGET_FBO)
	{
		glu::Texture colorAttachment(m_context.getRenderContext());

		gl.bindTexture(GL_TEXTURE_2D, *colorAttachment);
		gl.texStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, FBO_SIZE, FBO_SIZE);
		GLU_EXPECT_NO_ERROR(gl.getError(), "gen tex");

		m_fbo = de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(m_context.getRenderContext()));
		gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, **m_fbo);
		gl.framebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *colorAttachment, 0);
		GLU_EXPECT_NO_ERROR(gl.getError(), "attach");

		// unbind to prevent texture name deletion from removing it from current fbo attachments
		gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
	}

	{
		std::vector<tcu::Vec4> data;

		getAttributeData(data);

		m_vbo = de::MovePtr<glu::Buffer>(new glu::Buffer(m_context.getRenderContext()));
		gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
		gl.bufferData(GL_ARRAY_BUFFER, (int)(data.size() * sizeof(tcu::Vec4)), &data[0], GL_STATIC_DRAW);
		GLU_EXPECT_NO_ERROR(gl.getError(), "create vbo");
	}

	// Iterations
	for (int iterationNdx = 0; iterationNdx < m_numIterations; ++iterationNdx)
		m_iterationConfigs.push_back(generateConfig(iterationNdx, renderTargetSize));
}

void BBoxRenderCase::deinit (void)
{
	m_program.clear();
	m_vbo.clear();
	m_fbo.clear();
}

BBoxRenderCase::IterateResult BBoxRenderCase::iterate (void)
{
	const tcu::ScopedLogSection	section		(m_testCtx.getLog(),
											 std::string() + "Iteration" + de::toString((int)m_iteration),
											 std::string() + "Iteration " + de::toString((int)m_iteration+1) + "/" + de::toString((int)m_iterationConfigs.size()));
	const IterationConfig&		config		= m_iterationConfigs[m_iteration];

	// default
	if (m_iteration == 0)
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	renderTestPattern(config);
	verifyRenderResult(config);

	if (++m_iteration < (int)m_iterationConfigs.size())
		return CONTINUE;

	return STOP;
}

BBoxRenderCase::IterationConfig BBoxRenderCase::generateRandomConfig (int seed, const tcu::IVec2& renderTargetSize) const
{
	de::Random		rnd		(seed);
	IterationConfig	config;

	// viewport config
	config.viewportSize.x()	= rnd.getInt(MIN_VIEWPORT_SIZE, de::min<int>(renderTargetSize.x(), MAX_VIEWPORT_SIZE));
	config.viewportSize.y()	= rnd.getInt(MIN_VIEWPORT_SIZE, de::min<int>(renderTargetSize.y(), MAX_VIEWPORT_SIZE));
	config.viewportPos.x()	= rnd.getInt(0, renderTargetSize.x() - config.viewportSize.x());
	config.viewportPos.y()	= rnd.getInt(0, renderTargetSize.y() - config.viewportSize.y());

	// pattern location inside viewport
	config.patternSize.x()	= rnd.getFloat(0.4f, 1.4f);
	config.patternSize.y()	= rnd.getFloat(0.4f, 1.4f);
	config.patternPos.x()	= rnd.getFloat(-1.0f, 1.0f - config.patternSize.x());
	config.patternPos.y()	= rnd.getFloat(-1.0f, 1.0f - config.patternSize.y());

	// accurate bounding box
	config.bbox.min			= tcu::Vec4(config.patternPos.x(), config.patternPos.y(), 0.0f, 1.0f);
	config.bbox.max			= tcu::Vec4(config.patternPos.x() + config.patternSize.x(), config.patternPos.y() + config.patternSize.y(), 0.0f, 1.0f);

	if (m_bboxSize == BBOXSIZE_LARGER)
	{
		// increase bbox size
		config.bbox.min.x() -= rnd.getFloat() * 0.5f;
		config.bbox.min.y() -= rnd.getFloat() * 0.5f;
		config.bbox.min.z() -= rnd.getFloat() * 0.5f;

		config.bbox.max.x() += rnd.getFloat() * 0.5f;
		config.bbox.max.y() += rnd.getFloat() * 0.5f;
		config.bbox.max.z() += rnd.getFloat() * 0.5f;
	}
	else if (m_bboxSize == BBOXSIZE_SMALLER)
	{
		// reduce bbox size
		config.bbox.min.x() += rnd.getFloat() * 0.4f * config.patternSize.x();
		config.bbox.min.y() += rnd.getFloat() * 0.4f * config.patternSize.y();

		config.bbox.max.x() -= rnd.getFloat() * 0.4f * config.patternSize.x();
		config.bbox.max.y() -= rnd.getFloat() * 0.4f * config.patternSize.y();
	}

	return config;
}

tcu::IVec4 BBoxRenderCase::getViewportPatternArea (const tcu::Vec2& patternPos, const tcu::Vec2& patternSize, const tcu::IVec2& viewportSize, AABBRoundDirection roundDir) const
{
	const float	halfPixel	= 0.5f;
	tcu::Vec4	vertexBox;
	tcu::IVec4	pixelBox;

	vertexBox.x() = (patternPos.x() * 0.5f + 0.5f) * (float)viewportSize.x();
	vertexBox.y() = (patternPos.y() * 0.5f + 0.5f) * (float)viewportSize.y();
	vertexBox.z() = ((patternPos.x() + patternSize.x()) * 0.5f + 0.5f) * (float)viewportSize.x();
	vertexBox.w() = ((patternPos.y() + patternSize.y()) * 0.5f + 0.5f) * (float)viewportSize.y();

	if (roundDir == ROUND_INWARDS)
	{
		pixelBox.x() = (int)deFloatCeil(vertexBox.x()+halfPixel);
		pixelBox.y() = (int)deFloatCeil(vertexBox.y()+halfPixel);
		pixelBox.z() = (int)deFloatFloor(vertexBox.z()-halfPixel);
		pixelBox.w() = (int)deFloatFloor(vertexBox.w()-halfPixel);
	}
	else
	{
		pixelBox.x() = (int)deFloatFloor(vertexBox.x()-halfPixel);
		pixelBox.y() = (int)deFloatFloor(vertexBox.y()-halfPixel);
		pixelBox.z() = (int)deFloatCeil(vertexBox.z()+halfPixel);
		pixelBox.w() = (int)deFloatCeil(vertexBox.w()+halfPixel);
	}

	return pixelBox;
}

void BBoxRenderCase::setupRender (const IterationConfig& config)
{
	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	const glw::GLint		posLocation			= gl.getAttribLocation(m_program->getProgram(), "a_position");
	const glw::GLint		colLocation			= gl.getAttribLocation(m_program->getProgram(), "a_color");
	const glw::GLint		posScaleLocation	= gl.getUniformLocation(m_program->getProgram(), "u_posScale");

	TCU_CHECK(posLocation != -1);
	TCU_CHECK(colLocation != -1);
	TCU_CHECK(posScaleLocation != -1);

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Setting viewport to ("
			<< "x: " << config.viewportPos.x() << ", "
			<< "y: " << config.viewportPos.y() << ", "
			<< "w: " << config.viewportSize.x() << ", "
			<< "h: " << config.viewportSize.y() << ")\n"
		<< "Vertex coordinates are in range:\n"
			<< "\tx: [" << config.patternPos.x() << ", " << (config.patternPos.x() + config.patternSize.x()) << "]\n"
			<< "\ty: [" << config.patternPos.y() << ", " << (config.patternPos.y() + config.patternSize.y()) << "]\n"
		<< tcu::TestLog::EndMessage;

	if (!m_calcPerPrimitiveBBox)
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Setting primitive bounding box to:\n"
				<< "\t" << config.bbox.min << "\n"
				<< "\t" << config.bbox.max << "\n"
			<< tcu::TestLog::EndMessage;

	if (m_useGlobalState)
		gl.primitiveBoundingBox(config.bbox.min.x(), config.bbox.min.y(), config.bbox.min.z(), config.bbox.min.w(),
								config.bbox.max.x(), config.bbox.max.y(), config.bbox.max.z(), config.bbox.max.w());
	else
		// state is overriden by the tessellation output, set bbox to invisible area to imitiate dirty state left by application
		gl.primitiveBoundingBox(-2.0f, -2.0f, 0.0f, 1.0f,
								-1.7f, -1.7f, 0.0f, 1.0f);

	if (m_fbo)
		gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, **m_fbo);

	gl.viewport(config.viewportPos.x(), config.viewportPos.y(), config.viewportSize.x(), config.viewportSize.y());
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);

	gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, (int)(VA_NUM_ATTRIB_VECS * sizeof(float[4])), (const float*)DE_NULL + 4 * VA_POS_VEC_NDX);
	gl.vertexAttribPointer(colLocation, 4, GL_FLOAT, GL_FALSE, (int)(VA_NUM_ATTRIB_VECS * sizeof(float[4])), (const float*)DE_NULL + 4 * VA_COL_VEC_NDX);
	gl.enableVertexAttribArray(posLocation);
	gl.enableVertexAttribArray(colLocation);
	gl.useProgram(m_program->getProgram());
	gl.uniform4f(posScaleLocation, config.patternPos.x(), config.patternPos.y(), config.patternSize.x(), config.patternSize.y());

	{
		const glw::GLint bboxMinPos = gl.getUniformLocation(m_program->getProgram(), "u_primitiveBBoxMin");
		const glw::GLint bboxMaxPos = gl.getUniformLocation(m_program->getProgram(), "u_primitiveBBoxMax");

		gl.uniform4f(bboxMinPos, config.bbox.min.x(), config.bbox.min.y(), config.bbox.min.z(), config.bbox.min.w());
		gl.uniform4f(bboxMaxPos, config.bbox.max.x(), config.bbox.max.y(), config.bbox.max.z(), config.bbox.max.w());
	}

	gl.uniform2i(gl.getUniformLocation(m_program->getProgram(), "u_viewportPos"), config.viewportPos.x(), config.viewportPos.y());
	gl.uniform2i(gl.getUniformLocation(m_program->getProgram(), "u_viewportSize"), config.viewportSize.x(), config.viewportSize.y());

	GLU_EXPECT_NO_ERROR(gl.getError(), "setup");
}

const char* BBoxRenderCase::genShaderFunction (ShaderFunction func) const
{
	switch (func)
	{
		case SHADER_FUNC_MIRROR_X:
			return	"vec4 mirrorX(in highp vec4 p)\n"
					"{\n"
					"	highp vec2 patternOffset = u_posScale.xy;\n"
					"	highp vec2 patternScale = u_posScale.zw;\n"
					"	highp vec2 patternCenter = patternOffset + patternScale * 0.5;\n"
					"	return vec4(2.0 * patternCenter.x - p.x, p.y, p.z, p.w);\n"
					"}\n";

		case SHADER_FUNC_MIRROR_Y:
			return	"vec4 mirrorY(in highp vec4 p)\n"
					"{\n"
					"	highp vec2 patternOffset = u_posScale.xy;\n"
					"	highp vec2 patternScale = u_posScale.zw;\n"
					"	highp vec2 patternCenter = patternOffset + patternScale * 0.5;\n"
					"	return vec4(p.x, 2.0 * patternCenter.y - p.y, p.z, p.w);\n"
					"}\n";

		case SHADER_FUNC_INSIDE_BBOX:
			return	"uniform highp ivec2 u_viewportPos;\n"
					"uniform highp ivec2 u_viewportSize;\n"
					"flat in highp float v_bbox_expansionSize;\n"
					"flat in highp vec3 v_bbox_clipMin;\n"
					"flat in highp vec3 v_bbox_clipMax;\n"
					"\n"
					"bool fragmentInsideTheBBox(in highp float depth)\n"
					"{\n"
					"	highp vec4 wc = vec4(floor((v_bbox_clipMin.x * 0.5 + 0.5) * float(u_viewportSize.x) - v_bbox_expansionSize/2.0),\n"
					"	                     floor((v_bbox_clipMin.y * 0.5 + 0.5) * float(u_viewportSize.y) - v_bbox_expansionSize/2.0),\n"
					"	                     ceil((v_bbox_clipMax.x * 0.5 + 0.5) * float(u_viewportSize.x) + v_bbox_expansionSize/2.0),\n"
					"	                     ceil((v_bbox_clipMax.y * 0.5 + 0.5) * float(u_viewportSize.y) + v_bbox_expansionSize/2.0));\n"
					"	if (gl_FragCoord.x < float(u_viewportPos.x) + wc.x || gl_FragCoord.x > float(u_viewportPos.x) + wc.z ||\n"
					"	    gl_FragCoord.y < float(u_viewportPos.y) + wc.y || gl_FragCoord.y > float(u_viewportPos.y) + wc.w)\n"
					"	    return false;\n"
					"	const highp float dEpsilon = 0.001;\n"
					"	if (depth*2.0-1.0 < v_bbox_clipMin.z - dEpsilon || depth*2.0-1.0 > v_bbox_clipMax.z + dEpsilon)\n"
					"	    return false;\n"
					"	return true;\n"
					"}\n";
		default:
			DE_ASSERT(false);
			return "";
	}
}

class GridRenderCase : public BBoxRenderCase
{
public:
					GridRenderCase					(Context& context, const char* name, const char* description, deUint32 flags);
					~GridRenderCase					(void);

private:
	void			init							(void);

	std::string		genVertexSource					(void) const;
	std::string		genFragmentSource				(void) const;
	std::string		genTessellationControlSource	(void) const;
	std::string		genTessellationEvaluationSource	(void) const;
	std::string		genGeometrySource				(void) const;

	IterationConfig	generateConfig					(int iteration, const tcu::IVec2& renderTargetSize) const;
	void			getAttributeData				(std::vector<tcu::Vec4>& data) const;
	void			renderTestPattern				(const IterationConfig& config);
	void			verifyRenderResult				(const IterationConfig& config);

	const int		m_gridSize;
};

GridRenderCase::GridRenderCase (Context& context, const char* name, const char* description, deUint32 flags)
	: BBoxRenderCase	(context, name, description, 12, flags)
	, m_gridSize		(24)
{
}

GridRenderCase::~GridRenderCase (void)
{
}

void GridRenderCase::init (void)
{
	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Rendering yellow-green grid to " << ((m_renderTarget == RENDERTARGET_DEFAULT) ? ("default frame buffer") : ("fbo")) << ".\n"
		<< "Grid cells are in random order, varying grid size and location for each iteration.\n"
		<< "Marking all discardable fragments (fragments outside the bounding box) with a fully saturated blue channel."
		<< tcu::TestLog::EndMessage;

	BBoxRenderCase::init();
}

std::string GridRenderCase::genVertexSource (void) const
{
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"in highp vec4 a_position;\n"
			"in highp vec4 a_color;\n"
			"out highp vec4 vtx_color;\n"
			"uniform highp vec4 u_posScale;\n"
			"\n";
	if (!m_hasTessellationStage)
	{
		DE_ASSERT(m_useGlobalState);
		buf <<	"uniform highp vec4 u_primitiveBBoxMin;\n"
				"uniform highp vec4 u_primitiveBBoxMax;\n"
				"\n"
				"flat out highp float v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_expansionSize;\n"
				"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin;\n"
				"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax;\n"
				"\n";
	}

	buf <<	"void main()\n"
			"{\n"
			"	highp vec2 patternOffset = u_posScale.xy;\n"
			"	highp vec2 patternScale = u_posScale.zw;\n"
			"	gl_Position = vec4(a_position.xy * patternScale + patternOffset, a_position.z, a_position.w);\n"
			"	vtx_color = a_color;\n";

	if (!m_hasTessellationStage)
	{
		DE_ASSERT(m_useGlobalState);
		buf <<	"\n"
				"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_expansionSize = 0.0;\n"
				"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin =\n"
				"	    min(vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / u_primitiveBBoxMin.w,\n"
				"	        vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / u_primitiveBBoxMax.w);\n"
				"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax =\n"
				"	    min(vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / u_primitiveBBoxMin.w,\n"
				"	        vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / u_primitiveBBoxMax.w);\n";
	}

	buf<<	"}\n";

	return buf.str();
}

std::string GridRenderCase::genFragmentSource (void) const
{
	const char* const	colorInputName = (m_hasGeometryStage) ? ("geo_color") : (m_hasTessellationStage) ? ("tess_color") : ("vtx_color");
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"in mediump vec4 " << colorInputName << ";\n"
			"layout(location = 0) out mediump vec4 o_color;\n"
		<<	genShaderFunction(SHADER_FUNC_INSIDE_BBOX)
		<<	"\n"
			"void main()\n"
			"{\n"
			"	mediump vec4 baseColor = " << colorInputName << ";\n"
			"	mediump float blueChannel;\n"
			"	if (fragmentInsideTheBBox(gl_FragCoord.z))\n"
			"		blueChannel = 0.0;\n"
			"	else\n"
			"		blueChannel = 1.0;\n"
			"	o_color = vec4(baseColor.r, baseColor.g, blueChannel, baseColor.a);\n"
			"}\n";

	return buf.str();
}

std::string GridRenderCase::genTessellationControlSource (void) const
{
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			"#extension GL_EXT_primitive_bounding_box : require\n"
			"layout(vertices=3) out;\n"
			"\n"
			"in highp vec4 vtx_color[];\n"
			"out highp vec4 tess_ctrl_color[];\n"
			"uniform highp float u_tessellationLevel;\n"
			"uniform highp vec4 u_posScale;\n";

	if (!m_calcPerPrimitiveBBox)
	{
		buf <<	"uniform highp vec4 u_primitiveBBoxMin;\n"
				"uniform highp vec4 u_primitiveBBoxMax;\n";
	}

	buf <<	"patch out highp float vp_bbox_expansionSize;\n"
			"patch out highp vec3 vp_bbox_clipMin;\n"
			"patch out highp vec3 vp_bbox_clipMax;\n";

	if (m_calcPerPrimitiveBBox)
	{
		buf <<	"\n";
		if (m_hasGeometryStage)
			buf << genShaderFunction(SHADER_FUNC_MIRROR_X);
		buf << genShaderFunction(SHADER_FUNC_MIRROR_Y);

		buf <<	"vec4 transformVec(in highp vec4 p)\n"
				"{\n"
				"	return " << ((m_hasGeometryStage) ? ("mirrorX(mirrorY(p))") : ("mirrorY(p)")) << ";\n"
				"}\n";
	}

	buf <<	"\n"
			"void main()\n"
			"{\n"
			"	// convert to nonsensical coordinates, just in case\n"
			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position.wzxy;\n"
			"	tess_ctrl_color[gl_InvocationID] = vtx_color[gl_InvocationID];\n"
			"\n"
			"	gl_TessLevelOuter[0] = u_tessellationLevel;\n"
			"	gl_TessLevelOuter[1] = u_tessellationLevel;\n"
			"	gl_TessLevelOuter[2] = u_tessellationLevel;\n"
			"	gl_TessLevelInner[0] = u_tessellationLevel;\n";

	if (m_calcPerPrimitiveBBox)
	{
		buf <<	"\n"
				"	highp vec4 bboxMin = min(min(transformVec(gl_in[0].gl_Position),\n"
				"	                             transformVec(gl_in[1].gl_Position)),\n"
				"	                         transformVec(gl_in[2].gl_Position));\n"
				"	highp vec4 bboxMax = max(max(transformVec(gl_in[0].gl_Position),\n"
				"	                             transformVec(gl_in[1].gl_Position)),\n"
				"	                         transformVec(gl_in[2].gl_Position));\n";
	}
	else
	{
		buf <<	"\n"
				"	highp vec4 bboxMin = u_primitiveBBoxMin;\n"
				"	highp vec4 bboxMax = u_primitiveBBoxMax;\n";
	}

	if (!m_useGlobalState)
		buf <<	"\n"
				"	gl_BoundingBoxEXT[0] = bboxMin;\n"
				"	gl_BoundingBoxEXT[1] = bboxMax;\n";

	buf <<	"	vp_bbox_expansionSize = 0.0;\n"
			"	vp_bbox_clipMin = min(vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMin.w,\n"
			"	                      vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMax.w);\n"
			"	vp_bbox_clipMax = max(vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMin.w,\n"
			"	                      vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMax.w);\n"
			"}\n";

	return buf.str();
}

std::string GridRenderCase::genTessellationEvaluationSource (void) const
{
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			"#extension GL_EXT_gpu_shader5 : require\n"
			"layout(triangles) in;\n"
			"\n"
			"in highp vec4 tess_ctrl_color[];\n"
			"out highp vec4 tess_color;\n"
			"uniform highp vec4 u_posScale;\n"
			"patch in highp float vp_bbox_expansionSize;\n"
			"patch in highp vec3 vp_bbox_clipMin;\n"
			"patch in highp vec3 vp_bbox_clipMax;\n"
			"flat out highp float v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_expansionSize;\n"
			"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin;\n"
			"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax;\n"
			"\n"
			"precise gl_Position;\n"
			"\n"
		<<	genShaderFunction(SHADER_FUNC_MIRROR_Y)
		<<	"void main()\n"
			"{\n"
			"	// non-trivial tessellation evaluation shader, convert from nonsensical coords, flip vertically\n"
			"	gl_Position = mirrorY(gl_TessCoord.x * gl_in[0].gl_Position.zwyx +\n"
			"	                      gl_TessCoord.y * gl_in[1].gl_Position.zwyx +\n"
			"	                      gl_TessCoord.z * gl_in[2].gl_Position.zwyx);\n"
			"	tess_color = tess_ctrl_color[0];\n"
			"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_expansionSize = vp_bbox_expansionSize;\n"
			"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin = vp_bbox_clipMin;\n"
			"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax = vp_bbox_clipMax;\n"
			"}\n";

	return buf.str();
}

std::string GridRenderCase::genGeometrySource (void) const
{
	const char* const	colorInputName = (m_hasTessellationStage) ? ("tess_color") : ("vtx_color");
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_geometry_shader : require\n"
			"layout(triangles) in;\n"
			"layout(max_vertices=9, triangle_strip) out;\n"
			"\n"
			"in highp vec4 " << colorInputName << "[3];\n"
			"out highp vec4 geo_color;\n"
			"uniform highp vec4 u_posScale;\n"
			"\n"
			"flat in highp float v_geo_bbox_expansionSize[3];\n"
			"flat in highp vec3 v_geo_bbox_clipMin[3];\n"
			"flat in highp vec3 v_geo_bbox_clipMax[3];\n"
			"flat out highp vec3 v_bbox_clipMin;\n"
			"flat out highp vec3 v_bbox_clipMax;\n"
			"flat out highp float v_bbox_expansionSize;\n"
		<<	genShaderFunction(SHADER_FUNC_MIRROR_X)
		<<	"\n"
			"void setVisualizationVaryings()\n"
			"{\n"
			"	v_bbox_expansionSize = v_geo_bbox_expansionSize[0];\n"
			"	v_bbox_clipMin = v_geo_bbox_clipMin[0];\n"
			"	v_bbox_clipMax = v_geo_bbox_clipMax[0];\n"
			"}\n"
			"void main()\n"
			"{\n"
			"	// Non-trivial geometry shader: 1-to-3 amplification, mirror horizontally\n"
			"	highp vec4 p0 = mirrorX(gl_in[0].gl_Position);\n"
			"	highp vec4 p1 = mirrorX(gl_in[1].gl_Position);\n"
			"	highp vec4 p2 = mirrorX(gl_in[2].gl_Position);\n"
			"	highp vec4 pCentroid = vec4((p0.xyz + p1.xyz + p2.xyz) / 3.0, 1.0);\n"
			"	highp vec4 triangleColor = " << colorInputName << "[0];\n"
			"\n"
			"	gl_Position = p0; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
			"	gl_Position = p1; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
			"	gl_Position = pCentroid; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
			"	EndPrimitive();\n"
			"\n"
			"	gl_Position = p1; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
			"	gl_Position = p2; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
			"	gl_Position = pCentroid; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
			"	EndPrimitive();\n"
			"\n"
			"	gl_Position = p2; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
			"	gl_Position = p0; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
			"	gl_Position = pCentroid; geo_color = triangleColor; setVisualizationVaryings(); EmitVertex();\n"
			"	EndPrimitive();\n"
			"}\n";

	return buf.str();
}

GridRenderCase::IterationConfig GridRenderCase::generateConfig (int iteration, const tcu::IVec2& renderTargetSize) const
{
	return generateRandomConfig(0xDEDEDEu * (deUint32)iteration, renderTargetSize);
}

void GridRenderCase::getAttributeData (std::vector<tcu::Vec4>& data) const
{
	const tcu::Vec4		green				(0.0f, 1.0f, 0.0f, 1.0f);
	const tcu::Vec4		yellow				(1.0f, 1.0f, 0.0f, 1.0f);
	std::vector<int>	cellOrder			(m_gridSize * m_gridSize);
	de::Random			rnd					(0xDE56789);

	// generate grid with cells in random order
	for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
		cellOrder[ndx] = ndx;
	rnd.shuffle(cellOrder.begin(), cellOrder.end());

	data.resize(m_gridSize * m_gridSize * 6 * 2);
	for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
	{
		const int			cellNdx		= cellOrder[ndx];
		const int			cellX		= cellNdx % m_gridSize;
		const int			cellY		= cellNdx / m_gridSize;
		const tcu::Vec4&	cellColor	= ((cellX+cellY)%2 == 0) ? (green) : (yellow);

		data[(ndx * 6 + 0) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(float(cellX+0) / float(m_gridSize), float(cellY+0) / float(m_gridSize), 0.0f, 1.0f);
		data[(ndx * 6 + 0) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
		data[(ndx * 6 + 1) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(float(cellX+1) / float(m_gridSize), float(cellY+1) / float(m_gridSize), 0.0f, 1.0f);
		data[(ndx * 6 + 1) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
		data[(ndx * 6 + 2) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(float(cellX+0) / float(m_gridSize), float(cellY+1) / float(m_gridSize), 0.0f, 1.0f);
		data[(ndx * 6 + 2) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
		data[(ndx * 6 + 3) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(float(cellX+0) / float(m_gridSize), float(cellY+0) / float(m_gridSize), 0.0f, 1.0f);
		data[(ndx * 6 + 3) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
		data[(ndx * 6 + 4) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(float(cellX+1) / float(m_gridSize), float(cellY+0) / float(m_gridSize), 0.0f, 1.0f);
		data[(ndx * 6 + 4) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
		data[(ndx * 6 + 5) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(float(cellX+1) / float(m_gridSize), float(cellY+1) / float(m_gridSize), 0.0f, 1.0f);
		data[(ndx * 6 + 5) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = cellColor;
	}
}

void GridRenderCase::renderTestPattern (const IterationConfig& config)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	setupRender(config);

	if (m_hasTessellationStage)
	{
		const glw::GLint	tessLevelPos	= gl.getUniformLocation(m_program->getProgram(), "u_tessellationLevel");
		const glw::GLfloat	tessLevel		= 2.8f; // will be rounded up

		TCU_CHECK(tessLevelPos != -1);

		m_testCtx.getLog() << tcu::TestLog::Message << "u_tessellationLevel = " << tessLevel << tcu::TestLog::EndMessage;

		gl.uniform1f(tessLevelPos, tessLevel);
		gl.patchParameteri(GL_PATCH_VERTICES, 3);
		GLU_EXPECT_NO_ERROR(gl.getError(), "patch param");
	}

	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering grid." << tcu::TestLog::EndMessage;

	gl.drawArrays((m_hasTessellationStage) ? (GL_PATCHES) : (GL_TRIANGLES), 0, m_gridSize * m_gridSize * 6);
	GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
}

void GridRenderCase::verifyRenderResult (const IterationConfig& config)
{
	const glw::Functions&	gl						= m_context.getRenderContext().getFunctions();
	const ProjectedBBox		projectedBBox			= projectBoundingBox(config.bbox);
	const tcu::IVec4		viewportBBoxArea		= getViewportBoundingBoxArea(projectedBBox, config.viewportSize);
	const tcu::IVec4		viewportGridOuterArea	= getViewportPatternArea(config.patternPos, config.patternSize, config.viewportSize, ROUND_OUTWARDS);
	const tcu::IVec4		viewportGridInnerArea	= getViewportPatternArea(config.patternPos, config.patternSize, config.viewportSize, ROUND_INWARDS);
	tcu::Surface			viewportSurface			(config.viewportSize.x(), config.viewportSize.y());
	tcu::Surface			errorMask				(config.viewportSize.x(), config.viewportSize.y());
	bool					anyError				= false;

	if (!m_calcPerPrimitiveBBox)
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Projected bounding box: (clip space)\n"
				<< "\tx: [" << projectedBBox.min.x() << "," << projectedBBox.max.x() << "]\n"
				<< "\ty: [" << projectedBBox.min.y() << "," << projectedBBox.max.y() << "]\n"
				<< "\tz: [" << projectedBBox.min.z() << "," << projectedBBox.max.z() << "]\n"
			<< "In viewport coordinates:\n"
				<< "\tx: [" << viewportBBoxArea.x() << ", " << viewportBBoxArea.z() << "]\n"
				<< "\ty: [" << viewportBBoxArea.y() << ", " << viewportBBoxArea.w() << "]\n"
			<< "Verifying render results within the bounding box.\n"
			<< tcu::TestLog::EndMessage;
	else
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Verifying render result."
			<< tcu::TestLog::EndMessage;

	if (m_fbo)
		gl.bindFramebuffer(GL_READ_FRAMEBUFFER, **m_fbo);
	glu::readPixels(m_context.getRenderContext(), config.viewportPos.x(), config.viewportPos.y(), viewportSurface.getAccess());

	tcu::clear(errorMask.getAccess(), tcu::IVec4(0,0,0,255));

	for (int y = de::max(viewportBBoxArea.y(), 0); y < de::min(viewportBBoxArea.w(), config.viewportSize.y()); ++y)
	for (int x = de::max(viewportBBoxArea.x(), 0); x < de::min(viewportBBoxArea.z(), config.viewportSize.x()); ++x)
	{
		const tcu::RGBA	pixel		= viewportSurface.getPixel(x, y);
		const bool		outsideGrid	= x < viewportGridOuterArea.x() ||
									  y < viewportGridOuterArea.y() ||
									  x > viewportGridOuterArea.z() ||
									  y > viewportGridOuterArea.w();
		const bool		insideGrid	= x > viewportGridInnerArea.x() &&
									  y > viewportGridInnerArea.y() &&
									  x < viewportGridInnerArea.z() &&
									  y < viewportGridInnerArea.w();

		bool			error		= false;

		if (outsideGrid)
		{
			// expect black
			if (pixel.getRed() != 0 || pixel.getGreen() != 0 || pixel.getBlue() != 0)
				error = true;
		}

		else if (insideGrid)
		{
			// expect green, yellow or a combination of these
			if (pixel.getGreen() != 255 || pixel.getBlue() != 0)
				error = true;
		}
		else
		{
			// boundary, allow anything
		}

		if (error)
		{
			errorMask.setPixel(x, y, tcu::RGBA::red());
			anyError = true;
		}
	}

	if (anyError)
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Image verification failed."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
			<< tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
			<< tcu::TestLog::EndImageSet;

		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
	}
	else
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Result image ok."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
			<< tcu::TestLog::EndImageSet;
	}
}

class LineRenderCase : public BBoxRenderCase
{
public:
	enum
	{
		LINEFLAG_WIDE = 1u << FLAGBIT_USER_BIT,	//!< use wide lines
	};

					LineRenderCase					(Context& context, const char* name, const char* description, deUint32 flags);
					~LineRenderCase					(void);

private:
	enum
	{
		GREEN_COMPONENT_NDX = 1,
		BLUE_COMPONENT_NDX = 2,

		SCAN_ROW_COMPONENT_NDX = GREEN_COMPONENT_NDX, // \note: scans are orthogonal to the line
		SCAN_COL_COMPONENT_NDX = BLUE_COMPONENT_NDX,
	};

	enum QueryDirection
	{
		DIRECTION_HORIZONTAL = 0,
		DIRECTION_VERTICAL,
	};

	enum ScanResult
	{
		SCANRESULT_NUM_LINES_OK_BIT		= (1 << 0),
		SCANRESULT_LINE_WIDTH_OK_BIT	= (1 << 1),
		SCANRESULT_LINE_WIDTH_WARN_BIT	= (1 << 2),
		SCANRESULT_LINE_WIDTH_ERR_BIT	= (1 << 3),
		SCANRESULT_LINE_CONT_OK_BIT		= (1 << 4),
		SCANRESULT_LINE_CONT_ERR_BIT	= (1 << 5),
		SCANRESULT_LINE_CONT_WARN_BIT	= (1 << 6),
	};

	void				init							(void);

	std::string			genVertexSource					(void) const;
	std::string			genFragmentSource				(void) const;
	std::string			genTessellationControlSource	(void) const;
	std::string			genTessellationEvaluationSource	(void) const;
	std::string			genGeometrySource				(void) const;

	IterationConfig		generateConfig					(int iteration, const tcu::IVec2& renderTargetSize) const;
	void				getAttributeData				(std::vector<tcu::Vec4>& data) const;
	void				renderTestPattern				(const IterationConfig& config);
	void				verifyRenderResult				(const IterationConfig& config);

	tcu::IVec2			getNumberOfLinesRange			(int queryAreaBegin, int queryAreaEnd, float patternStart, float patternSize, int viewportArea, QueryDirection queryDir) const;
	deUint8				scanRow							(const tcu::ConstPixelBufferAccess& access, int row, int rowBegin, int rowEnd, int rowViewportBegin, int rowViewportEnd, const tcu::IVec2& numLines, int& floodCounter) const;
	deUint8				scanColumn						(const tcu::ConstPixelBufferAccess& access, int column, int columnBegin, int columnEnd, int columnViewportBegin, int columnViewportEnd, const tcu::IVec2& numLines, int& floodCounter) const;
	bool				checkAreaNumLines				(const tcu::ConstPixelBufferAccess& access, const tcu::IVec4& area, int& floodCounter, int componentNdx, const tcu::IVec2& numLines) const;
	deUint8				checkLineContinuity				(const tcu::ConstPixelBufferAccess& access, const tcu::IVec2& begin, const tcu::IVec2& end, int componentNdx, int& messageLimitCounter) const;
	tcu::IVec2			getNumMinimaMaxima				(const tcu::ConstPixelBufferAccess& access, int componentNdx) const;
	deUint8				checkLineWidths					(const tcu::ConstPixelBufferAccess& access, const tcu::IVec2& begin, const tcu::IVec2& end, int componentNdx, int& floodCounter) const;
	void				printLineWidthError				(const tcu::IVec2& pos, int detectedLineWidth, const tcu::IVec2& lineWidthRange, bool isHorizontal, int& floodCounter) const;

	const int			m_patternSide;
	const bool			m_isWideLineCase;
	const int			m_wideLineLineWidth;
};

LineRenderCase::LineRenderCase (Context& context, const char* name, const char* description, deUint32 flags)
	: BBoxRenderCase		(context, name, description, 12, flags)
	, m_patternSide			(12)
	, m_isWideLineCase		((flags & LINEFLAG_WIDE) != 0)
	, m_wideLineLineWidth	(5)
{
}

LineRenderCase::~LineRenderCase (void)
{
}

void LineRenderCase::init (void)
{
	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Rendering line pattern to " << ((m_renderTarget == RENDERTARGET_DEFAULT) ? ("default frame buffer") : ("fbo")) << ".\n"
		<< "Vertical lines are green, horizontal lines blue. Using additive blending.\n"
		<< "Line segments are in random order, varying pattern size and location for each iteration.\n"
		<< "Marking all discardable fragments (fragments outside the bounding box) with a fully saturated red channel."
		<< tcu::TestLog::EndMessage;

	if (m_isWideLineCase)
	{
		glw::GLfloat lineWidthRange[2] = {0.0f, 0.0f};
		m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lineWidthRange);

		if (lineWidthRange[1] < (float)m_wideLineLineWidth)
			throw tcu::NotSupportedError("Test requires line width " + de::toString(m_wideLineLineWidth));
	}

	BBoxRenderCase::init();
}

std::string LineRenderCase::genVertexSource (void) const
{
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"in highp vec4 a_position;\n"
			"in highp vec4 a_color;\n"
			"out highp vec4 vtx_color;\n"
			"uniform highp vec4 u_posScale;\n"
			"uniform highp float u_lineWidth;\n"
			"\n";
	if (!m_hasTessellationStage)
	{
		DE_ASSERT(m_useGlobalState);
		buf <<	"uniform highp vec4 u_primitiveBBoxMin;\n"
				"uniform highp vec4 u_primitiveBBoxMax;\n"
				"\n"
				"flat out highp float v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_expansionSize;\n"
				"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin;\n"
				"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax;\n"
				"\n";
	}
	buf <<	"void main()\n"
			"{\n"
			"	highp vec2 patternOffset = u_posScale.xy;\n"
			"	highp vec2 patternScale = u_posScale.zw;\n"
			"	gl_Position = vec4(a_position.xy * patternScale + patternOffset, a_position.z, a_position.w);\n"
			"	vtx_color = a_color;\n";
	if (!m_hasTessellationStage)
	{
		DE_ASSERT(m_useGlobalState);
		buf <<	"\n"
				"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_expansionSize = u_lineWidth;\n"
				"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin =\n"
				"	    min(vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / u_primitiveBBoxMin.w,\n"
				"	        vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / u_primitiveBBoxMax.w);\n"
				"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax =\n"
				"	    min(vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / u_primitiveBBoxMin.w,\n"
				"	        vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / u_primitiveBBoxMax.w);\n";
	}
	buf <<	"}\n";

	return buf.str();
}

std::string LineRenderCase::genFragmentSource (void) const
{
	const char* const	colorInputName = (m_hasGeometryStage) ? ("geo_color") : (m_hasTessellationStage) ? ("tess_color") : ("vtx_color");
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"in mediump vec4 " << colorInputName << ";\n"
			"layout(location = 0) out mediump vec4 o_color;\n"
		<<	genShaderFunction(SHADER_FUNC_INSIDE_BBOX)
		<<	"\n"
			"void main()\n"
			"{\n"
			"	mediump vec4 baseColor = " << colorInputName << ";\n"
			"	mediump float redChannel;\n"
			"	if (fragmentInsideTheBBox(gl_FragCoord.z))\n"
			"		redChannel = 0.0;\n"
			"	else\n"
			"		redChannel = 1.0;\n"
			"	o_color = vec4(redChannel, baseColor.g, baseColor.b, baseColor.a);\n"
			"}\n";

	return buf.str();
}

std::string LineRenderCase::genTessellationControlSource (void) const
{
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			"#extension GL_EXT_primitive_bounding_box : require\n"
			"layout(vertices=2) out;"
			"\n"
			"in highp vec4 vtx_color[];\n"
			"out highp vec4 tess_ctrl_color[];\n"
			"uniform highp float u_tessellationLevel;\n"
			"uniform highp vec4 u_posScale;\n"
			"uniform highp float u_lineWidth;\n";

	if (!m_calcPerPrimitiveBBox)
	{
		buf <<	"uniform highp vec4 u_primitiveBBoxMin;\n"
				"uniform highp vec4 u_primitiveBBoxMax;\n";
	}

	buf <<	"patch out highp float vp_bbox_expansionSize;\n"
			"patch out highp vec3 vp_bbox_clipMin;\n"
			"patch out highp vec3 vp_bbox_clipMax;\n";

	if (m_calcPerPrimitiveBBox)
	{
		buf <<	"\n";
		if (m_hasGeometryStage)
			buf << genShaderFunction(SHADER_FUNC_MIRROR_X);
		buf << genShaderFunction(SHADER_FUNC_MIRROR_Y);

		buf <<	"vec4 transformVec(in highp vec4 p)\n"
				"{\n"
				"	return " << ((m_hasGeometryStage) ? ("mirrorX(mirrorY(p))") : ("mirrorY(p)")) << ";\n"
				"}\n";
	}

	buf <<	"\n"
			"void main()\n"
			"{\n"
			"	// convert to nonsensical coordinates, just in case\n"
			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position.wzxy;\n"
			"	tess_ctrl_color[gl_InvocationID] = vtx_color[gl_InvocationID];\n"
			"\n"
			"	gl_TessLevelOuter[0] = 0.8; // will be rounded up to 1\n"
			"	gl_TessLevelOuter[1] = u_tessellationLevel;\n";

	if (m_calcPerPrimitiveBBox)
	{
		buf <<	"\n"
				"	highp vec4 bboxMin = min(transformVec(gl_in[0].gl_Position),\n"
				"	                         transformVec(gl_in[1].gl_Position));\n"
				"	highp vec4 bboxMax = max(transformVec(gl_in[0].gl_Position),\n"
				"	                         transformVec(gl_in[1].gl_Position));\n";
	}
	else
	{
		buf <<	"\n"
				"	highp vec4 bboxMin = u_primitiveBBoxMin;\n"
				"	highp vec4 bboxMax = u_primitiveBBoxMax;\n";
	}

	if (!m_useGlobalState)
		buf <<	"\n"
				"	gl_BoundingBoxEXT[0] = bboxMin;\n"
				"	gl_BoundingBoxEXT[1] = bboxMax;\n";

	buf <<	"	vp_bbox_expansionSize = u_lineWidth;\n"
			"	vp_bbox_clipMin = min(vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMin.w,\n"
			"	                      vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMax.w);\n"
			"	vp_bbox_clipMax = max(vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMin.w,\n"
			"	                      vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMax.w);\n"
			"}\n";

	return buf.str();
}

std::string LineRenderCase::genTessellationEvaluationSource (void) const
{
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			"layout(isolines) in;"
			"\n"
			"in highp vec4 tess_ctrl_color[];\n"
			"out highp vec4 tess_color;\n"
			"uniform highp vec4 u_posScale;\n"
			"\n"
			"patch in highp float vp_bbox_expansionSize;\n"
			"patch in highp vec3 vp_bbox_clipMin;\n"
			"patch in highp vec3 vp_bbox_clipMax;\n"
			"flat out highp float v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_expansionSize;\n"
			"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin;\n"
			"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax;\n"
		<<	genShaderFunction(SHADER_FUNC_MIRROR_Y)
		<<	"void main()\n"
			"{\n"
			"	// non-trivial tessellation evaluation shader, convert from nonsensical coords, flip vertically\n"
			"	gl_Position = mirrorY(mix(gl_in[0].gl_Position.zwyx, gl_in[1].gl_Position.zwyx, gl_TessCoord.x));\n"
			"	tess_color = tess_ctrl_color[0];\n"
			"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_expansionSize = vp_bbox_expansionSize;\n"
			"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin = vp_bbox_clipMin;\n"
			"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax = vp_bbox_clipMax;\n"
			"}\n";

	return buf.str();
}

std::string LineRenderCase::genGeometrySource (void) const
{
	const char* const	colorInputName = (m_hasTessellationStage) ? ("tess_color") : ("vtx_color");
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_geometry_shader : require\n"
			"layout(lines) in;\n"
			"layout(max_vertices=5, line_strip) out;\n"
			"\n"
			"in highp vec4 " << colorInputName << "[2];\n"
			"out highp vec4 geo_color;\n"
			"uniform highp vec4 u_posScale;\n"
			"\n"
			"\n"
			"flat in highp float v_geo_bbox_expansionSize[2];\n"
			"flat in highp vec3 v_geo_bbox_clipMin[2];\n"
			"flat in highp vec3 v_geo_bbox_clipMax[2];\n"
			"flat out highp vec3 v_bbox_clipMin;\n"
			"flat out highp vec3 v_bbox_clipMax;\n"
			"flat out highp float v_bbox_expansionSize;\n"
		<<	genShaderFunction(SHADER_FUNC_MIRROR_X)
		<<	"\n"
			"void setVisualizationVaryings()\n"
			"{\n"
			"	v_bbox_expansionSize = v_geo_bbox_expansionSize[0];\n"
			"	v_bbox_clipMin = v_geo_bbox_clipMin[0];\n"
			"	v_bbox_clipMax = v_geo_bbox_clipMax[0];\n"
			"}\n"
			"void main()\n"
			"{\n"
			"	// Non-trivial geometry shader: 1-to-3 amplification, mirror horizontally\n"
			"	highp vec4 p0 = mirrorX(gl_in[0].gl_Position);\n"
			"	highp vec4 p1 = mirrorX(gl_in[1].gl_Position);\n"
			"	highp vec4 lineColor = " << colorInputName << "[0];\n"
			"\n"
			"	// output two separate primitives, just in case\n"
			"	gl_Position = mix(p0, p1, 0.00); geo_color = lineColor; setVisualizationVaryings(); EmitVertex();\n"
			"	gl_Position = mix(p0, p1, 0.33); geo_color = lineColor; setVisualizationVaryings(); EmitVertex();\n"
			"	EndPrimitive();\n"
			"\n"
			"	gl_Position = mix(p0, p1, 0.33); geo_color = lineColor; setVisualizationVaryings(); EmitVertex();\n"
			"	gl_Position = mix(p0, p1, 0.67); geo_color = lineColor; setVisualizationVaryings(); EmitVertex();\n"
			"	gl_Position = mix(p0, p1, 1.00); geo_color = lineColor; setVisualizationVaryings(); EmitVertex();\n"
			"	EndPrimitive();\n"
			"}\n";

	return buf.str();
}

LineRenderCase::IterationConfig LineRenderCase::generateConfig (int iteration, const tcu::IVec2& renderTargetSize) const
{
	const int numMaxAttempts = 128;

	// Avoid too narrow viewports, lines could merge together. Require viewport is at least 2.5x the size of the line bodies.
	for (int attemptNdx = 0; attemptNdx < numMaxAttempts; ++attemptNdx)
	{
		const IterationConfig& config = generateRandomConfig((0xDEDEDEu * (deUint32)iteration) ^ (0xABAB13 * attemptNdx), renderTargetSize);

		if ((float)config.viewportSize.x() * (config.patternSize.x() * 0.5f) > 2.5f * (float)m_patternSide * (float)m_wideLineLineWidth &&
			(float)config.viewportSize.y() * (config.patternSize.y() * 0.5f) > 2.5f * (float)m_patternSide * (float)m_wideLineLineWidth)
		{
			return config;
		}
	}

	DE_ASSERT(false);
	return IterationConfig();
}

void LineRenderCase::getAttributeData (std::vector<tcu::Vec4>& data) const
{
	const tcu::Vec4		green		(0.0f, 1.0f, 0.0f, 1.0f);
	const tcu::Vec4		blue		(0.0f, 0.0f, 1.0f, 1.0f);
	std::vector<int>	cellOrder	(m_patternSide * m_patternSide * 2);
	de::Random			rnd			(0xDE12345);

	// generate crosshatch pattern with segments in random order
	for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
		cellOrder[ndx] = ndx;
	rnd.shuffle(cellOrder.begin(), cellOrder.end());

	data.resize(cellOrder.size() * 4);
	for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
	{
		const int segmentID		= cellOrder[ndx];
		const int direction		= segmentID & 0x01;
		const int majorCoord	= (segmentID >> 1) / m_patternSide;
		const int minorCoord	= (segmentID >> 1) % m_patternSide;

		if (direction)
		{
			data[(ndx * 2 + 0) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(float(minorCoord) / float(m_patternSide), float(majorCoord) / float(m_patternSide), 0.0f, 1.0f);
			data[(ndx * 2 + 0) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = green;
			data[(ndx * 2 + 1) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(float(minorCoord) / float(m_patternSide), float(majorCoord + 1) / float(m_patternSide), 0.0f, 1.0f);
			data[(ndx * 2 + 1) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = green;
		}
		else
		{
			data[(ndx * 2 + 0) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(float(majorCoord) / float(m_patternSide), float(minorCoord) / float(m_patternSide), 0.0f, 1.0f);
			data[(ndx * 2 + 0) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = blue;
			data[(ndx * 2 + 1) * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(float(majorCoord + 1) / float(m_patternSide), float(minorCoord) / float(m_patternSide), 0.0f, 1.0f);
			data[(ndx * 2 + 1) * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = blue;
		}
	}
}

void LineRenderCase::renderTestPattern (const IterationConfig& config)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	setupRender(config);

	if (m_hasTessellationStage)
	{
		const glw::GLint	tessLevelPos	= gl.getUniformLocation(m_program->getProgram(), "u_tessellationLevel");
		const glw::GLfloat	tessLevel		= 2.8f; // will be rounded up

		TCU_CHECK(tessLevelPos != -1);

		m_testCtx.getLog() << tcu::TestLog::Message << "u_tessellationLevel = " << tessLevel << tcu::TestLog::EndMessage;

		gl.uniform1f(tessLevelPos, tessLevel);
		gl.patchParameteri(GL_PATCH_VERTICES, 2);
		GLU_EXPECT_NO_ERROR(gl.getError(), "patch param");
	}

	if (m_isWideLineCase)
		gl.lineWidth((float)m_wideLineLineWidth);

	gl.uniform1f(gl.getUniformLocation(m_program->getProgram(), "u_lineWidth"), (m_isWideLineCase) ? ((float)m_wideLineLineWidth) : (1.0f));

	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering pattern." << tcu::TestLog::EndMessage;

	gl.enable(GL_BLEND);
	gl.blendFunc(GL_ONE, GL_ONE);
	gl.blendEquation(GL_FUNC_ADD);

	gl.drawArrays((m_hasTessellationStage) ? (GL_PATCHES) : (GL_LINES), 0, m_patternSide * m_patternSide * 2 * 2);
	GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
}

void LineRenderCase::verifyRenderResult (const IterationConfig& config)
{
	const glw::Functions&	gl							= m_context.getRenderContext().getFunctions();
	const bool				isMsaa						= m_context.getRenderTarget().getNumSamples() > 1;
	const ProjectedBBox		projectedBBox				= projectBoundingBox(config.bbox);
	const float				lineWidth					= (m_isWideLineCase) ? ((float)m_wideLineLineWidth) : (1.0f);
	const tcu::IVec4		viewportBBoxArea			= getViewportBoundingBoxArea(projectedBBox, config.viewportSize, lineWidth);
	const tcu::IVec4		viewportPatternArea			= getViewportPatternArea(config.patternPos, config.patternSize, config.viewportSize, ROUND_INWARDS);
	const tcu::IVec2		expectedHorizontalLines		= getNumberOfLinesRange(viewportBBoxArea.y(), viewportBBoxArea.w(), config.patternPos.y(), config.patternSize.y(), config.viewportSize.y(), DIRECTION_VERTICAL);
	const tcu::IVec2		expectedVerticalLines		= getNumberOfLinesRange(viewportBBoxArea.x(), viewportBBoxArea.z(), config.patternPos.x(), config.patternSize.x(), config.viewportSize.x(), DIRECTION_HORIZONTAL);
	const tcu::IVec4		verificationArea			= tcu::IVec4(de::max(viewportBBoxArea.x(), 0),
																	 de::max(viewportBBoxArea.y(), 0),
																	 de::min(viewportBBoxArea.z(), config.viewportSize.x()),
																	 de::min(viewportBBoxArea.w(), config.viewportSize.y()));

	tcu::Surface			viewportSurface				(config.viewportSize.x(), config.viewportSize.y());
	int						messageLimitCounter			= 8;

	enum ScanResultCodes
	{
		SCANRESULT_NUM_LINES_ERR	= 0,
		SCANRESULT_LINE_WIDTH_MSAA	= 1,
		SCANRESULT_LINE_WIDTH_WARN	= 2,
		SCANRESULT_LINE_WIDTH_ERR	= 3,
		SCANRESULT_LINE_CONT_ERR	= 4,
		SCANRESULT_LINE_CONT_WARN	= 5,
		SCANRESULT_LINE_LAST
	};

	int						rowScanResult[SCANRESULT_LINE_LAST]		= {0, 0, 0, 0, 0, 0};
	int						columnScanResult[SCANRESULT_LINE_LAST]	= {0, 0, 0, 0, 0, 0};
	bool					anyError								= false;
	bool					msaaRelaxationRequired					= false;
	bool					hwIssueRelaxationRequired				= false;

	if (!m_calcPerPrimitiveBBox)
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Projected bounding box: (clip space)\n"
				<< "\tx: [" << projectedBBox.min.x() << "," << projectedBBox.max.x() << "]\n"
				<< "\ty: [" << projectedBBox.min.y() << "," << projectedBBox.max.y() << "]\n"
				<< "\tz: [" << projectedBBox.min.z() << "," << projectedBBox.max.z() << "]\n"
			<< "In viewport coordinates:\n"
				<< "\tx: [" << viewportBBoxArea.x() << ", " << viewportBBoxArea.z() << "]\n"
				<< "\ty: [" << viewportBBoxArea.y() << ", " << viewportBBoxArea.w() << "]\n"
			<< "Verifying render results within the bounding box:\n"
			<< tcu::TestLog::EndMessage;
	else
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Verifying render result:"
			<< tcu::TestLog::EndMessage;

	m_testCtx.getLog()
		<< tcu::TestLog::Message
			<< "\tCalculating number of horizontal and vertical lines within the bounding box, expecting:\n"
			<< "\t[" << expectedHorizontalLines.x() << ", " << expectedHorizontalLines.y() << "] horizontal lines.\n"
			<< "\t[" << expectedVerticalLines.x() << ", " << expectedVerticalLines.y() << "] vertical lines.\n"
		<< tcu::TestLog::EndMessage;

	if (m_fbo)
		gl.bindFramebuffer(GL_READ_FRAMEBUFFER, **m_fbo);
	glu::readPixels(m_context.getRenderContext(), config.viewportPos.x(), config.viewportPos.y(), viewportSurface.getAccess());

	// scan rows
	for (int y = de::max(verificationArea.y(), viewportPatternArea.y()); y < de::min(verificationArea.w(), viewportPatternArea.w()); ++y)
	{
		const deUint8 result = scanRow(viewportSurface.getAccess(),
									   y,
									   verificationArea.x(),
									   verificationArea.z(),
									   de::max(verificationArea.x(), viewportPatternArea.x()),
									   de::min(verificationArea.z(), viewportPatternArea.z()),
									   expectedVerticalLines,
									   messageLimitCounter);

		if ((result & SCANRESULT_NUM_LINES_OK_BIT) == 0)
			rowScanResult[SCANRESULT_NUM_LINES_ERR]++;
		if ((result & SCANRESULT_LINE_CONT_OK_BIT) == 0)
		{
			if ((result & SCANRESULT_LINE_CONT_WARN_BIT) != 0)
				rowScanResult[SCANRESULT_LINE_CONT_WARN]++;
			else
				rowScanResult[SCANRESULT_LINE_CONT_ERR]++;
		}
		else if ((result & SCANRESULT_LINE_WIDTH_OK_BIT) == 0)
		{
			if (m_isWideLineCase && isMsaa)
			{
				// multisampled wide lines might not be supported
				rowScanResult[SCANRESULT_LINE_WIDTH_MSAA]++;
			}
			else if ((result & SCANRESULT_LINE_WIDTH_ERR_BIT) == 0 &&
					 (result & SCANRESULT_LINE_WIDTH_WARN_BIT) != 0)
			{
				rowScanResult[SCANRESULT_LINE_WIDTH_WARN]++;
			}
			else
				rowScanResult[SCANRESULT_LINE_WIDTH_ERR]++;
		}
	}

	// scan columns
	for (int x = de::max(verificationArea.x(), viewportPatternArea.x()); x < de::min(verificationArea.z(), viewportPatternArea.z()); ++x)
	{
		const deUint8 result = scanColumn(viewportSurface.getAccess(),
										  x,
										  verificationArea.y(),
										  verificationArea.w(),
										  de::min(verificationArea.y(), viewportPatternArea.y()),
										  de::min(verificationArea.w(), viewportPatternArea.w()),
										  expectedHorizontalLines,
										  messageLimitCounter);

		if ((result & SCANRESULT_NUM_LINES_OK_BIT) == 0)
			columnScanResult[SCANRESULT_NUM_LINES_ERR]++;
		if ((result & SCANRESULT_LINE_CONT_OK_BIT) == 0)
		{
			if ((result & SCANRESULT_LINE_CONT_WARN_BIT) != 0)
				columnScanResult[SCANRESULT_LINE_CONT_WARN]++;
			else
				columnScanResult[SCANRESULT_LINE_CONT_ERR]++;
		}
		else if ((result & SCANRESULT_LINE_WIDTH_OK_BIT) == 0)
		{
			if (m_isWideLineCase && isMsaa)
			{
				// multisampled wide lines might not be supported
				columnScanResult[SCANRESULT_LINE_WIDTH_MSAA]++;
			}
			else if ((result & SCANRESULT_LINE_WIDTH_ERR_BIT) == 0 &&
					 (result & SCANRESULT_LINE_WIDTH_WARN_BIT) != 0)
			{
				columnScanResult[SCANRESULT_LINE_WIDTH_WARN]++;
			}
			else
				columnScanResult[SCANRESULT_LINE_WIDTH_ERR]++;
		}
	}

	if (columnScanResult[SCANRESULT_LINE_WIDTH_ERR] != 0 || rowScanResult[SCANRESULT_LINE_WIDTH_ERR] != 0)
		anyError = true;
	else if(columnScanResult[SCANRESULT_LINE_CONT_ERR] != 0 || rowScanResult[SCANRESULT_LINE_CONT_ERR] != 0)
		anyError = true;
	else if (columnScanResult[SCANRESULT_LINE_WIDTH_MSAA] != 0 || rowScanResult[SCANRESULT_LINE_WIDTH_MSAA] != 0)
		msaaRelaxationRequired = true;
	else if (columnScanResult[SCANRESULT_LINE_WIDTH_WARN] != 0 || rowScanResult[SCANRESULT_LINE_WIDTH_WARN] != 0)
		hwIssueRelaxationRequired = true;
	else if (columnScanResult[SCANRESULT_NUM_LINES_ERR] != 0)
	{
		// found missing lines in a columnw and row line continuity check reported a warning (not an error) -> line width precision issue
		if (rowScanResult[SCANRESULT_LINE_CONT_ERR] == 0 && rowScanResult[SCANRESULT_LINE_CONT_WARN])
			hwIssueRelaxationRequired = true;
		else
			anyError = true;
	}
	else if (rowScanResult[SCANRESULT_NUM_LINES_ERR] != 0)
	{
		// found missing lines in a row and column line continuity check reported a warning (not an error) -> line width precision issue
		if (columnScanResult[SCANRESULT_LINE_CONT_ERR] == 0 && columnScanResult[SCANRESULT_LINE_CONT_WARN])
			hwIssueRelaxationRequired = true;
		else
			anyError = true;
	}

	if (anyError || msaaRelaxationRequired || hwIssueRelaxationRequired)
	{
		if (messageLimitCounter < 0)
			m_testCtx.getLog() << tcu::TestLog::Message << "Omitted " << (-messageLimitCounter) << " row/column error descriptions." << tcu::TestLog::EndMessage;

		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Image verification failed."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
			<< tcu::TestLog::EndImageSet;

		if (anyError)
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
		else if (hwIssueRelaxationRequired)
		{
			// Line width hw issue
			m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, "Line width verification failed");
		}
		else
		{
			// MSAA wide lines are optional
			m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Multisampled wide line verification failed");
		}
	}
	else
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Result image ok."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
			<< tcu::TestLog::EndImageSet;
	}
}

tcu::IVec2 LineRenderCase::getNumberOfLinesRange (int queryAreaBegin, int queryAreaEnd, float patternStart, float patternSize, int viewportArea, QueryDirection queryDir) const
{
	// pattern is not symmetric due to mirroring
	const int	patternStartNdx	= (queryDir == DIRECTION_HORIZONTAL) ? ((m_hasGeometryStage) ? (1) : (0)) : ((m_hasTessellationStage) ? (1) : (0));
	const int	patternEndNdx	= patternStartNdx + m_patternSide;

	int			numLinesMin		= 0;
	int			numLinesMax		= 0;

	for (int lineNdx = patternStartNdx; lineNdx < patternEndNdx; ++lineNdx)
	{
		const float linePos		= (patternStart + (float(lineNdx) / float(m_patternSide)) * patternSize) * 0.5f + 0.5f;
		const float lineWidth	= (m_isWideLineCase) ? ((float)m_wideLineLineWidth) : (1.0f);

		if (linePos * (float)viewportArea > (float)queryAreaBegin + 1.0f &&
			linePos * (float)viewportArea < (float)queryAreaEnd   - 1.0f)
		{
			// line center is within the area
			++numLinesMin;
			++numLinesMax;
		}
		else if (linePos * (float)viewportArea > (float)queryAreaBegin - lineWidth*0.5f - 1.0f &&
		         linePos * (float)viewportArea < (float)queryAreaEnd   + lineWidth*0.5f + 1.0f)
		{
			// line could leak into area
			++numLinesMax;
		}
	}

	return tcu::IVec2(numLinesMin, numLinesMax);
}

deUint8 LineRenderCase::scanRow (const tcu::ConstPixelBufferAccess& access, int row, int rowBegin, int rowEnd, int rowViewportBegin, int rowViewportEnd, const tcu::IVec2& numLines, int& messageLimitCounter) const
{
	const bool		numLinesOk			= checkAreaNumLines(access, tcu::IVec4(rowBegin, row, rowEnd - rowBegin, 1), messageLimitCounter, SCAN_ROW_COMPONENT_NDX, numLines);
	const deUint8	lineWidthRes		= checkLineWidths(access, tcu::IVec2(rowBegin, row), tcu::IVec2(rowEnd, row), SCAN_ROW_COMPONENT_NDX, messageLimitCounter);
	const deUint8	lineContinuityRes	= checkLineContinuity(access, tcu::IVec2(rowViewportBegin, row), tcu::IVec2(rowViewportEnd, row), SCAN_COL_COMPONENT_NDX, messageLimitCounter);
	deUint8			result				= 0;

	if (numLinesOk)
		result |= (deUint8)SCANRESULT_NUM_LINES_OK_BIT;

	if (lineContinuityRes == 0)
		result |= (deUint8)SCANRESULT_LINE_CONT_OK_BIT;
	else
		result |= lineContinuityRes;

	if (lineWidthRes == 0)
		result |= (deUint8)SCANRESULT_LINE_WIDTH_OK_BIT;
	else
		result |= lineWidthRes;

	return result;
}

deUint8 LineRenderCase::scanColumn (const tcu::ConstPixelBufferAccess& access, int column, int columnBegin, int columnEnd, int columnViewportBegin, int columnViewportEnd, const tcu::IVec2& numLines, int& messageLimitCounter) const
{
	const bool		numLinesOk			= checkAreaNumLines(access, tcu::IVec4(column, columnBegin, 1, columnEnd - columnBegin), messageLimitCounter, SCAN_COL_COMPONENT_NDX, numLines);
	const deUint8	lineWidthRes		= checkLineWidths(access, tcu::IVec2(column, columnBegin), tcu::IVec2(column, columnEnd), SCAN_COL_COMPONENT_NDX, messageLimitCounter);
	const deUint8	lineContinuityRes	= checkLineContinuity(access, tcu::IVec2(column, columnViewportBegin), tcu::IVec2(column, columnViewportEnd), SCAN_ROW_COMPONENT_NDX, messageLimitCounter);
	deUint8			result				= 0;

	if (numLinesOk)
		result |= (deUint8)SCANRESULT_NUM_LINES_OK_BIT;

	if (lineContinuityRes == 0)
		result |= (deUint8)SCANRESULT_LINE_CONT_OK_BIT;
	else
		result |= lineContinuityRes;

	if (lineWidthRes == 0)
		result |= (deUint8)SCANRESULT_LINE_WIDTH_OK_BIT;
	else
		result |= lineWidthRes;

	return result;
}

bool LineRenderCase::checkAreaNumLines (const tcu::ConstPixelBufferAccess& access, const tcu::IVec4& area, int& messageLimitCounter, int componentNdx, const tcu::IVec2& numLines) const
{
	// Num maxima == num lines
	const tcu::ConstPixelBufferAccess	subAccess		= tcu::getSubregion(access, area.x(), area.y(), 0, area.z(), area.w(), 1);
	const tcu::IVec2					numMinimaMaxima	= getNumMinimaMaxima(subAccess, componentNdx);
	const int							numMaxima		= numMinimaMaxima.y();

	// In valid range
	if (numMaxima >= numLines.x() && numMaxima <= numLines.y())
		return true;

	if (--messageLimitCounter < 0)
		return false;

	if (area.z() == 1)
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "On column " << area.x() << ", y: [" << area.y() << "," << (area.y()+area.w()) << "):\n"
				<< "\tExpected [" << numLines.x() << ", " << numLines.y() << "] lines but the number of lines in the area is " << numMaxima
			<< tcu::TestLog::EndMessage;
	else
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "On row " << area.y() << ", x: [" << area.x() << "," << (area.x()+area.z()) << "):\n"
				<< "\tExpected [" << numLines.x() << ", " << numLines.y() << "] lines but the number of lines in the area is " << numMaxima
			<< tcu::TestLog::EndMessage;

	return false;
}

tcu::IVec2 LineRenderCase::getNumMinimaMaxima (const tcu::ConstPixelBufferAccess& access, int componentNdx) const
{
	DE_ASSERT(access.getWidth() == 1 || access.getHeight() == 1);

	int previousValue	= -1;
	int previousSign	= 0;
	int numMinima		= 0;
	int numMaxima		= 0;

	for (int y = 0; y < access.getHeight(); ++y)
	for (int x = 0; x < access.getWidth(); ++x)
	{
		const int componentValue = access.getPixelInt(x, y)[componentNdx];

		if (previousValue != -1)
		{
			const int sign = (componentValue > previousValue) ? (+1) : (componentValue < previousValue) ? (-1) : (0);

			// local minima/maxima in sign changes (zero signless)
			if (sign != 0 && sign == -previousSign)
			{
				previousSign = sign;

				if (sign > 0)
					++numMinima;
				else
					++numMaxima;
			}
			else if (sign != 0 && previousSign == 0)
			{
				previousSign = sign;

				// local extreme at the start boundary
				if (sign > 0)
					++numMinima;
				else
					++numMaxima;
			}
		}

		previousValue = componentValue;
	}

	// local extreme at the end boundary
	if (previousSign > 0)
		++numMaxima;
	else if (previousSign < 0)
		++numMinima;
	else
	{
		++numMaxima;
		++numMinima;
	}

	return tcu::IVec2(numMinima, numMaxima);
}

deUint8 LineRenderCase::checkLineContinuity (const tcu::ConstPixelBufferAccess& access, const tcu::IVec2& begin, const tcu::IVec2& end, int componentNdx, int& messageLimitCounter) const
{
	bool				line					= false;
	const tcu::IVec2	advance					= (begin.x() == end.x()) ? (tcu::IVec2(0, 1)) : (tcu::IVec2(1, 0));
	int					missedPixels			= 0;
	int					totalPixels				= 0;
	deUint8				errorMask				= 0;

	for (tcu::IVec2 cursor = begin; cursor != end; cursor += advance)
	{
		const bool hit = (access.getPixelInt(cursor.x(), cursor.y())[componentNdx] != 0);

		if (hit)
			line = true;
		else if (line && !hit)
		{
			// non-continuous line detected
			const tcu::IVec2 advanceNeighbor	= tcu::IVec2(1, 1) - advance;
			const tcu::IVec2 cursorNeighborPos	= cursor + advanceNeighbor;
			const tcu::IVec2 cursorNeighborNeg	= cursor - advanceNeighbor;
			// hw precision issues may lead to a line being non-straight -> check neighboring pixels
			if ((access.getPixelInt(cursorNeighborPos.x(), cursorNeighborPos.y())[componentNdx] == 0) && (access.getPixelInt(cursorNeighborNeg.x(), cursorNeighborNeg.y())[componentNdx] == 0))
				++missedPixels;
		}
		++totalPixels;
	}

	if (missedPixels > 0)
	{
		if (--messageLimitCounter >= 0)
		{
			m_testCtx.getLog()
				<< tcu::TestLog::Message
				<< "Found non-continuous " << ((advance.x() == 1)  ? ("horizontal") : ("vertical")) << " line near " << begin << ". "
				<< "Missed pixels: " << missedPixels
				<< tcu::TestLog::EndMessage;
		}
		// allow 10% missing pixels for warning
		if (missedPixels <= deRoundFloatToInt32((float)totalPixels * 0.1f))
			errorMask = SCANRESULT_LINE_CONT_WARN_BIT;
		else
			errorMask =  SCANRESULT_LINE_CONT_ERR_BIT;
	}

	return errorMask;
}

deUint8 LineRenderCase::checkLineWidths (const tcu::ConstPixelBufferAccess& access, const tcu::IVec2& begin, const tcu::IVec2& end, int componentNdx, int& messageLimitCounter) const
{
	const bool			multisample				= m_context.getRenderTarget().getNumSamples() > 1;
	const int			lineRenderWidth			= (m_isWideLineCase) ? (m_wideLineLineWidth) : 1;
	const tcu::IVec2	lineWidthRange			= (multisample)
													? (tcu::IVec2(lineRenderWidth, lineRenderWidth+1))	// multisampled "smooth" lines may spread to neighboring pixel
													: (tcu::IVec2(lineRenderWidth, lineRenderWidth));
	const tcu::IVec2	relaxedLineWidthRange	= (tcu::IVec2(lineRenderWidth-1, lineRenderWidth+1));

	int					lineWidth				= 0;
	bool				bboxLimitedLine			= false;
	deUint8				errorMask				= 0;

	const tcu::IVec2	advance					= (begin.x() == end.x()) ? (tcu::IVec2(0, 1)) : (tcu::IVec2(1, 0));

	// fragments before begin?
	if (access.getPixelInt(begin.x(), begin.y())[componentNdx] != 0)
	{
		bboxLimitedLine = true;

		for (tcu::IVec2 cursor = begin - advance;; cursor -= advance)
		{
			if (cursor.x() < 0 || cursor.y() < 0)
			{
				break;
			}
			else if (access.getPixelInt(cursor.x(), cursor.y())[componentNdx] != 0)
			{
				++lineWidth;
			}
			else
				break;
		}
	}

	for (tcu::IVec2 cursor = begin; cursor != end; cursor += advance)
	{
		const bool hit = (access.getPixelInt(cursor.x(), cursor.y())[componentNdx] != 0);

		if (hit)
			++lineWidth;
		else if (lineWidth)
		{
			// Line is allowed to be be thinner if it borders the bbox boundary (since part of the line might have been discarded).
			const bool incorrectLineWidth = (lineWidth < lineWidthRange.x() && !bboxLimitedLine) || (lineWidth > lineWidthRange.y());

			if (incorrectLineWidth)
			{
				const bool incorrectRelaxedLineWidth = (lineWidth < relaxedLineWidthRange.x() && !bboxLimitedLine) || (lineWidth > relaxedLineWidthRange.y());

				if (incorrectRelaxedLineWidth)
					errorMask |= SCANRESULT_LINE_WIDTH_ERR_BIT;
				else
					errorMask |= SCANRESULT_LINE_WIDTH_WARN_BIT;

				printLineWidthError(cursor, lineWidth, lineWidthRange, advance.x() == 0, messageLimitCounter);
			}

			lineWidth = 0;
			bboxLimitedLine = false;
		}
	}

	// fragments after end?
	if (lineWidth)
	{
		for (tcu::IVec2 cursor = end;; cursor += advance)
		{
			if (cursor.x() >= access.getWidth() || cursor.y() >= access.getHeight())
			{
				if (lineWidth > lineWidthRange.y())
				{
					if (lineWidth > relaxedLineWidthRange.y())
						errorMask |= SCANRESULT_LINE_WIDTH_ERR_BIT;
					else
						errorMask |= SCANRESULT_LINE_WIDTH_WARN_BIT;

					printLineWidthError(cursor, lineWidth, lineWidthRange, advance.x() == 0, messageLimitCounter);
				}

				break;
			}
			else if (access.getPixelInt(cursor.x(), cursor.y())[componentNdx] != 0)
			{
				++lineWidth;
			}
			else if (lineWidth)
			{
				// only check that line width is not larger than expected. Line width may be smaller
				// since the scanning 'cursor' is now outside the bounding box.
				const bool incorrectLineWidth = (lineWidth > lineWidthRange.y());

				if (incorrectLineWidth)
				{
					const bool incorrectRelaxedLineWidth = (lineWidth > relaxedLineWidthRange.y());

					if (incorrectRelaxedLineWidth)
						errorMask |= SCANRESULT_LINE_WIDTH_ERR_BIT;
					else
						errorMask |= SCANRESULT_LINE_WIDTH_WARN_BIT;

					printLineWidthError(cursor, lineWidth, lineWidthRange, advance.x() == 0, messageLimitCounter);
				}

				lineWidth = 0;
			}
		}
	}

	return errorMask;
}

void LineRenderCase::printLineWidthError (const tcu::IVec2& pos, int detectedLineWidth, const tcu::IVec2& lineWidthRange, bool isHorizontal, int& messageLimitCounter) const
{
	if (--messageLimitCounter < 0)
		return;

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Found incorrect line width near " << pos << ": (" << ((isHorizontal) ? ("horizontal") : ("vertical")) << " line)\n"
			<< "\tExpected line width in range [" << lineWidthRange.x() << ", " << lineWidthRange.y() << "] but found " << detectedLineWidth
		<< tcu::TestLog::EndMessage;
}

class PointRenderCase : public BBoxRenderCase
{
public:
	enum
	{
		POINTFLAG_WIDE = 1u << FLAGBIT_USER_BIT,	//!< use wide points
	};
	struct GeneratedPoint
	{
		tcu::Vec2	center;
		int			size;
		bool		even;
	};

							PointRenderCase					(Context& context, const char* name, const char* description, deUint32 flags);
							~PointRenderCase				(void);

private:
	enum ResultPointType
	{
		POINT_FULL = 0,
		POINT_PARTIAL
	};

	void					init							(void);
	void					deinit							(void);

	std::string				genVertexSource					(void) const;
	std::string				genFragmentSource				(void) const;
	std::string				genTessellationControlSource	(void) const;
	std::string				genTessellationEvaluationSource	(void) const;
	std::string				genGeometrySource				(void) const;

	IterationConfig			generateConfig					(int iteration, const tcu::IVec2& renderTargetSize) const;
	void					generateAttributeData			(void);
	void					getAttributeData				(std::vector<tcu::Vec4>& data) const;
	void					renderTestPattern				(const IterationConfig& config);
	void					verifyRenderResult				(const IterationConfig& config);

	void					genReferencePointData			(const IterationConfig& config, std::vector<GeneratedPoint>& data) const;
	bool					verifyNarrowPointPattern		(const tcu::Surface& viewport, const std::vector<GeneratedPoint>& refPoints, const ProjectedBBox& bbox, int& logFloodCounter);
	bool					verifyWidePointPattern			(const tcu::Surface& viewport, const std::vector<GeneratedPoint>& refPoints, const ProjectedBBox& bbox, int& logFloodCounter);
	bool					verifyWidePoint					(const tcu::Surface& viewport, const GeneratedPoint& refPoint, const ProjectedBBox& bbox, ResultPointType pointType, int& logFloodCounter);
	bool					verifyWidePointAt				(const tcu::IVec2& pointPos, const tcu::Surface& viewport, const GeneratedPoint& refPoint, const tcu::IVec4& bbox, ResultPointType pointType, int componentNdx, int& logFloodCounter);
	tcu::IVec2				scanPointWidthAt				(const tcu::IVec2& pointPos, const tcu::Surface& viewport, int expectedPointSize, int componentNdx) const;

	const int				m_numStripes;
	const bool				m_isWidePointCase;
	std::vector<tcu::Vec4>	m_attribData;
};

PointRenderCase::PointRenderCase (Context& context, const char* name, const char* description, deUint32 flags)
	: BBoxRenderCase	(context, name, description, 12, flags)
	, m_numStripes		(4)
	, m_isWidePointCase	((flags & POINTFLAG_WIDE) != 0)
{
}

PointRenderCase::~PointRenderCase (void)
{
}

void PointRenderCase::init (void)
{
	if (m_isWidePointCase)
	{
		// extensions
		if (m_hasGeometryStage && !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_point_size"))
			throw tcu::NotSupportedError("Test requires GL_EXT_geometry_point_size extension");
		if (m_hasTessellationStage && !m_hasGeometryStage && !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_point_size"))
			throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_point_size extension");

		// point size range
		{
			glw::GLfloat pointSizeRange[2] = {0.0f, 0.0f};
			m_context.getRenderContext().getFunctions().getFloatv(GL_ALIASED_POINT_SIZE_RANGE, pointSizeRange);

			if (pointSizeRange[1] < 5.0f)
				throw tcu::NotSupportedError("Test requires point size 5.0");
		}
	}

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Rendering point pattern to " << ((m_renderTarget == RENDERTARGET_DEFAULT) ? ("default frame buffer") : ("fbo")) << ".\n"
		<< "Half of the points are green, half blue. Using additive blending.\n"
		<< "Points are in random order, varying pattern size and location for each iteration.\n"
		<< "Marking all discardable fragments (fragments outside the bounding box) with a fully saturated red channel."
		<< tcu::TestLog::EndMessage;

	generateAttributeData();

	BBoxRenderCase::init();
}

void PointRenderCase::deinit (void)
{
	// clear data
	m_attribData = std::vector<tcu::Vec4>();

	// deinit parent
	BBoxRenderCase::deinit();
}

std::string PointRenderCase::genVertexSource (void) const
{
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"in highp vec4 a_position;\n"
			"in highp vec4 a_color;\n"
			"out highp vec4 vtx_color;\n"
			"uniform highp vec4 u_posScale;\n"
			"\n";
	if (!m_hasTessellationStage)
	{
		DE_ASSERT(m_useGlobalState);
		buf <<	"uniform highp vec4 u_primitiveBBoxMin;\n"
				"uniform highp vec4 u_primitiveBBoxMax;\n"
				"\n"
				"flat out highp float v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_expansionSize;\n"
				"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin;\n"
				"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax;\n"
				"\n";
	}

	buf <<	"void main()\n"
			"{\n"
			"	highp vec2 patternOffset = u_posScale.xy;\n"
			"	highp vec2 patternScale = u_posScale.zw;\n"
			"	highp float pointSize = "
					<< ((m_isWidePointCase && !m_hasTessellationStage && !m_hasGeometryStage) ? ("(a_color.g > 0.0) ? (5.0) : (3.0)") : ("1.0"))
					<< ";\n"
		<<	"	gl_Position = vec4(a_position.xy * patternScale + patternOffset, a_position.z, a_position.w);\n"
			"	gl_PointSize = pointSize;\n"
			"	vtx_color = a_color;\n";

	if (!m_hasTessellationStage)
	{
		DE_ASSERT(m_useGlobalState);
		buf <<	"\n"
				"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_expansionSize = pointSize;\n"
				"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin =\n"
				"	    min(vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / u_primitiveBBoxMin.w,\n"
				"	        vec3(u_primitiveBBoxMin.x, u_primitiveBBoxMin.y, u_primitiveBBoxMin.z) / u_primitiveBBoxMax.w);\n"
				"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax =\n"
				"	    min(vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / u_primitiveBBoxMin.w,\n"
				"	        vec3(u_primitiveBBoxMax.x, u_primitiveBBoxMax.y, u_primitiveBBoxMax.z) / u_primitiveBBoxMax.w);\n";
	}

	buf <<	"}\n";
	return buf.str();
}

std::string PointRenderCase::genFragmentSource (void) const
{
	const char* const	colorInputName = (m_hasGeometryStage) ? ("geo_color") : (m_hasTessellationStage) ? ("tess_color") : ("vtx_color");
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"in mediump vec4 " << colorInputName << ";\n"
			"layout(location = 0) out mediump vec4 o_color;\n"
		<<	genShaderFunction(SHADER_FUNC_INSIDE_BBOX)
		<<	"\n"
			"void main()\n"
			"{\n"
			"	mediump vec4 baseColor = " << colorInputName << ";\n"
			"	mediump float redChannel;\n"
			"	if (fragmentInsideTheBBox(gl_FragCoord.z))\n"
			"		redChannel = 0.0;\n"
			"	else\n"
			"		redChannel = 1.0;\n"
			"	o_color = vec4(redChannel, baseColor.g, baseColor.b, baseColor.a);\n"
			"}\n";

	return buf.str();
}

std::string PointRenderCase::genTessellationControlSource (void) const
{
	const bool			tessellationWidePoints = (m_isWidePointCase) && (!m_hasGeometryStage);
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			"#extension GL_EXT_primitive_bounding_box : require\n"
		<<	((tessellationWidePoints) ? ("#extension GL_EXT_tessellation_point_size : require\n") : (""))
		<<	"layout(vertices=1) out;"
			"\n"
			"in highp vec4 vtx_color[];\n"
			"out highp vec4 tess_ctrl_color[];\n"
			"uniform highp float u_tessellationLevel;\n"
			"uniform highp vec4 u_posScale;\n";

	if (!m_calcPerPrimitiveBBox)
	{
		buf <<	"uniform highp vec4 u_primitiveBBoxMin;\n"
				"uniform highp vec4 u_primitiveBBoxMax;\n";
	}

	buf <<	"patch out highp vec3 vp_bbox_clipMin;\n"
			"patch out highp vec3 vp_bbox_clipMax;\n";

	if (m_calcPerPrimitiveBBox)
	{
		buf <<	"\n";
		if (m_hasGeometryStage)
			buf << genShaderFunction(SHADER_FUNC_MIRROR_X);
		buf << genShaderFunction(SHADER_FUNC_MIRROR_Y);

		buf <<	"vec4 transformVec(in highp vec4 p)\n"
				"{\n"
				"	return " << ((m_hasGeometryStage) ? ("mirrorX(mirrorY(p))") : ("mirrorY(p)")) << ";\n"
				"}\n";
	}

	buf <<	"\n"
			"void main()\n"
			"{\n"
			"	// convert to nonsensical coordinates, just in case\n"
			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position.wzxy;\n"
			"	tess_ctrl_color[gl_InvocationID] = vtx_color[gl_InvocationID];\n"
			"\n"
			"	gl_TessLevelOuter[0] = u_tessellationLevel;\n"
			"	gl_TessLevelOuter[1] = u_tessellationLevel;\n"
			"	gl_TessLevelOuter[2] = u_tessellationLevel;\n"
			"	gl_TessLevelOuter[3] = u_tessellationLevel;\n"
			"	gl_TessLevelInner[0] = 0.8; // will be rounded up to 1\n"
			"	gl_TessLevelInner[1] = 0.8; // will be rounded up to 1\n";

	if (m_calcPerPrimitiveBBox)
	{
		buf <<	"\n";

		if (m_hasGeometryStage)
			buf <<	"	const vec2 minExpansion = vec2(0.07 + 0.05, 0.07 + 0.02); // eval and geometry shader\n"
					"	const vec2 maxExpansion = vec2(0.07 + 0.05, 0.07 + 0.03); // eval and geometry shader\n";
		else
			buf <<	"	const vec2 minExpansion = vec2(0.07, 0.07); // eval shader\n"
					"	const vec2 maxExpansion = vec2(0.07, 0.07); // eval shader\n";

		buf <<	"	highp vec2 patternScale = u_posScale.zw;\n"
				"	highp vec4 bboxMin = transformVec(gl_in[0].gl_Position) - vec4(minExpansion * patternScale, 0.0, 0.0);\n"
				"	highp vec4 bboxMax = transformVec(gl_in[0].gl_Position) + vec4(maxExpansion * patternScale, 0.0, 0.0);\n";
	}
	else
	{
		buf <<	"\n"
				"	highp vec4 bboxMin = u_primitiveBBoxMin;\n"
				"	highp vec4 bboxMax = u_primitiveBBoxMax;\n";
	}
	if (!m_useGlobalState)
		buf <<	"\n"
				"	gl_BoundingBoxEXT[0] = bboxMin;\n"
				"	gl_BoundingBoxEXT[1] = bboxMax;\n";

	buf <<	"	vp_bbox_clipMin = min(vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMin.w,\n"
			"	                      vec3(bboxMin.x, bboxMin.y, bboxMin.z) / bboxMax.w);\n"
			"	vp_bbox_clipMax = max(vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMin.w,\n"
			"	                      vec3(bboxMax.x, bboxMax.y, bboxMax.z) / bboxMax.w);\n"
			"}\n";

	return buf.str();
}

std::string PointRenderCase::genTessellationEvaluationSource (void) const
{
	const bool			tessellationWidePoints = (m_isWidePointCase) && (!m_hasGeometryStage);
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
		<<	((tessellationWidePoints) ? ("#extension GL_EXT_tessellation_point_size : require\n") : (""))
		<<	"layout(quads, point_mode) in;"
			"\n"
			"in highp vec4 tess_ctrl_color[];\n"
			"out highp vec4 tess_color;\n"
			"uniform highp vec4 u_posScale;\n"
			"\n"
			"patch in highp vec3 vp_bbox_clipMin;\n"
			"patch in highp vec3 vp_bbox_clipMax;\n"
		<<	((!m_hasGeometryStage) ? ("flat out highp float v_bbox_expansionSize;\n") : (""))
		<<	"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin;\n"
			"flat out highp vec3 v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax;\n"
			"\n"
		<<	genShaderFunction(SHADER_FUNC_MIRROR_Y)
		<<	"void main()\n"
			"{\n"
			"	// non-trivial tessellation evaluation shader, convert from nonsensical coords, flip vertically\n"
			"	highp vec2 patternScale = u_posScale.zw;\n"
			"	highp vec4 offset = vec4((gl_TessCoord.xy * 2.0 - vec2(1.0)) * 0.07 * patternScale, 0.0, 0.0);\n"
			"	highp float pointSize = " << ((tessellationWidePoints) ? ("(tess_ctrl_color[0].g > 0.0) ? (5.0) : (3.0)") : ("1.0")) << ";\n"
			"	gl_Position = mirrorY(gl_in[0].gl_Position.zwyx + offset);\n";

	if (tessellationWidePoints)
		buf << "	gl_PointSize = pointSize;\n";

	buf <<	"	tess_color = tess_ctrl_color[0];\n"
		<<	((!m_hasGeometryStage) ? ("v_bbox_expansionSize = pointSize;\n") : (""))
		<<	"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMin = vp_bbox_clipMin;\n"
			"	v_" << (m_hasGeometryStage ? "geo_" : "") << "bbox_clipMax = vp_bbox_clipMax;\n"
			"}\n";

	return buf.str();
}

std::string PointRenderCase::genGeometrySource (void) const
{
	const char* const	colorInputName = (m_hasTessellationStage) ? ("tess_color") : ("vtx_color");
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_geometry_shader : require\n"
		<<	((m_isWidePointCase) ? ("#extension GL_EXT_geometry_point_size : require\n") : (""))
		<<	"layout(points) in;\n"
			"layout(max_vertices=3, points) out;\n"
			"\n"
			"in highp vec4 " << colorInputName << "[1];\n"
			"out highp vec4 geo_color;\n"
			"uniform highp vec4 u_posScale;\n"
			"\n"
			"flat in highp vec3 v_geo_bbox_clipMin[1];\n"
			"flat in highp vec3 v_geo_bbox_clipMax[1];\n"
			"flat out highp vec3 v_bbox_clipMin;\n"
			"flat out highp vec3 v_bbox_clipMax;\n"
			"flat out highp float v_bbox_expansionSize;\n"
			"\n"
		<<	genShaderFunction(SHADER_FUNC_MIRROR_X)
		<<	"\n"
			"void main()\n"
			"{\n"
			"	// Non-trivial geometry shader: 1-to-3 amplification, mirror horizontally\n"
			"	highp vec4 p0 = mirrorX(gl_in[0].gl_Position);\n"
			"	highp vec4 pointColor = " << colorInputName << "[0];\n"
			"	highp vec2 patternScale = u_posScale.zw;\n"
			"	highp float pointSize = "
				<< (m_isWidePointCase ? ("(pointColor.g > 0.0) ? (5.0) : (3.0)") : ("1.0"))
				<< ";\n"
			"\n"
			"	highp vec4 offsets[3] =\n"
			"		vec4[3](\n"
			"			vec4( 0.05 * patternScale.x, 0.03 * patternScale.y, 0.0, 0.0),\n"
			"			vec4(-0.01 * patternScale.x,-0.02 * patternScale.y, 0.0, 0.0),\n"
			"			vec4(-0.05 * patternScale.x, 0.02 * patternScale.y, 0.0, 0.0)\n"
			"		);\n"
			"	for (int ndx = 0; ndx < 3; ++ndx)\n"
			"	{\n"
			"		gl_Position = p0 + offsets[ndx];\n";

	if (m_isWidePointCase)
		buf <<	"		gl_PointSize = pointSize;\n";

	buf <<	"		v_bbox_clipMin = v_geo_bbox_clipMin[0];\n"
			"		v_bbox_clipMax = v_geo_bbox_clipMax[0];\n"
			"		v_bbox_expansionSize = pointSize;\n"
			"		geo_color = pointColor;\n"
			"		EmitVertex();\n"
			"	}\n"
			"}\n";

	return buf.str();
}

PointRenderCase::IterationConfig PointRenderCase::generateConfig (int iteration, const tcu::IVec2& renderTargetSize) const
{
	IterationConfig config = generateRandomConfig(0xDEDEDEu * (deUint32)iteration, renderTargetSize);

	// equal or larger -> expand according to shader expansion
	if (m_bboxSize == BBOXSIZE_EQUAL || m_bboxSize == BBOXSIZE_LARGER)
	{
		const tcu::Vec2 patternScale = config.patternSize;

		if (m_hasTessellationStage)
		{
			config.bbox.min -= tcu::Vec4(0.07f * patternScale.x(), 0.07f * patternScale.y(), 0.0f, 0.0f);
			config.bbox.max += tcu::Vec4(0.07f * patternScale.x(), 0.07f * patternScale.y(), 0.0f, 0.0f);
		}
		if (m_hasGeometryStage)
		{
			config.bbox.min -= tcu::Vec4(0.05f * patternScale.x(), 0.02f * patternScale.y(), 0.0f, 0.0f);
			config.bbox.max += tcu::Vec4(0.05f * patternScale.x(), 0.03f * patternScale.y(), 0.0f, 0.0f);
		}
	}

	return config;
}

void PointRenderCase::generateAttributeData (void)
{
	const tcu::Vec4		green		(0.0f, 1.0f, 0.0f, 1.0f);
	const tcu::Vec4		blue		(0.0f, 0.0f, 1.0f, 1.0f);
	std::vector<int>	cellOrder	(m_numStripes * m_numStripes * 2);
	de::Random			rnd			(0xDE22446);

	for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
		cellOrder[ndx] = ndx;
	rnd.shuffle(cellOrder.begin(), cellOrder.end());

	m_attribData.resize(cellOrder.size() * 2);
	for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
	{
		const int pointID		= cellOrder[ndx];
		const int direction		= pointID & 0x01;
		const int majorCoord	= (pointID >> 1) / m_numStripes;
		const int minorCoord	= (pointID >> 1) % m_numStripes;

		if (direction)
		{
			m_attribData[ndx * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(float(minorCoord) / float(m_numStripes), float(majorCoord) / float(m_numStripes), 0.0f, 1.0f);
			m_attribData[ndx * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = green;
		}
		else
		{
			m_attribData[ndx * VA_NUM_ATTRIB_VECS + VA_POS_VEC_NDX] = tcu::Vec4(((float)majorCoord + 0.5f) / float(m_numStripes), ((float)minorCoord + 0.5f) / float(m_numStripes), 0.0f, 1.0f);
			m_attribData[ndx * VA_NUM_ATTRIB_VECS + VA_COL_VEC_NDX] = blue;
		}
	}
}

void PointRenderCase::getAttributeData (std::vector<tcu::Vec4>& data) const
{
	data = m_attribData;
}

void PointRenderCase::renderTestPattern (const IterationConfig& config)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	setupRender(config);

	if (m_hasTessellationStage)
	{
		const glw::GLint	tessLevelPos	= gl.getUniformLocation(m_program->getProgram(), "u_tessellationLevel");
		const glw::GLfloat	tessLevel		= 0.8f; // will be rounded up

		TCU_CHECK(tessLevelPos != -1);

		m_testCtx.getLog() << tcu::TestLog::Message << "u_tessellationLevel = " << tessLevel << tcu::TestLog::EndMessage;

		gl.uniform1f(tessLevelPos, tessLevel);
		gl.patchParameteri(GL_PATCH_VERTICES, 1);
		GLU_EXPECT_NO_ERROR(gl.getError(), "patch param");
	}

	m_testCtx.getLog() << tcu::TestLog::Message << "Rendering pattern." << tcu::TestLog::EndMessage;

	gl.enable(GL_BLEND);
	gl.blendFunc(GL_ONE, GL_ONE);
	gl.blendEquation(GL_FUNC_ADD);

	gl.drawArrays((m_hasTessellationStage) ? (GL_PATCHES) : (GL_POINTS), 0, m_numStripes * m_numStripes * 2);
	GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
}

void PointRenderCase::verifyRenderResult (const IterationConfig& config)
{
	const glw::Functions&		gl						= m_context.getRenderContext().getFunctions();
	const ProjectedBBox			projectedBBox			= projectBoundingBox(config.bbox);
	const tcu::IVec4			viewportBBoxArea		= getViewportBoundingBoxArea(projectedBBox, config.viewportSize);

	tcu::Surface				viewportSurface			(config.viewportSize.x(), config.viewportSize.y());
	int							logFloodCounter			= 8;
	bool						anyError;
	std::vector<GeneratedPoint>	refPoints;

	if (!m_calcPerPrimitiveBBox)
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Projected bounding box: (clip space)\n"
				<< "\tx: [" << projectedBBox.min.x() << "," << projectedBBox.max.x() << "]\n"
				<< "\ty: [" << projectedBBox.min.y() << "," << projectedBBox.max.y() << "]\n"
				<< "\tz: [" << projectedBBox.min.z() << "," << projectedBBox.max.z() << "]\n"
			<< "In viewport coordinates:\n"
				<< "\tx: [" << viewportBBoxArea.x() << ", " << viewportBBoxArea.z() << "]\n"
				<< "\ty: [" << viewportBBoxArea.y() << ", " << viewportBBoxArea.w() << "]\n"
			<< "Verifying render results within the bounding box:\n"
			<< tcu::TestLog::EndMessage;
	else
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Verifying render result:"
			<< tcu::TestLog::EndMessage;

	if (m_fbo)
		gl.bindFramebuffer(GL_READ_FRAMEBUFFER, **m_fbo);
	glu::readPixels(m_context.getRenderContext(), config.viewportPos.x(), config.viewportPos.y(), viewportSurface.getAccess());

	genReferencePointData(config, refPoints);

	if (m_isWidePointCase)
		anyError = verifyWidePointPattern(viewportSurface, refPoints, projectedBBox, logFloodCounter);
	else
		anyError = verifyNarrowPointPattern(viewportSurface, refPoints, projectedBBox, logFloodCounter);

	if (anyError)
	{
		if (logFloodCounter < 0)
			m_testCtx.getLog() << tcu::TestLog::Message << "Omitted " << (-logFloodCounter) << " error descriptions." << tcu::TestLog::EndMessage;

		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Image verification failed."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
			<< tcu::TestLog::EndImageSet;

		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
	}
	else
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Result image ok."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Viewport", "Viewport contents", viewportSurface.getAccess())
			<< tcu::TestLog::EndImageSet;
	}
}

struct PointSorter
{
	bool operator() (const PointRenderCase::GeneratedPoint& a, const PointRenderCase::GeneratedPoint& b) const
	{
		if (a.center.y() < b.center.y())
			return true;
		else if (a.center.y() > b.center.y())
			return false;
		else
			return (a.center.x() < b.center.x());
	}
};

void PointRenderCase::genReferencePointData (const IterationConfig& config, std::vector<GeneratedPoint>& data) const
{
	std::vector<GeneratedPoint> currentPoints;

	// vertex shader
	currentPoints.resize(m_attribData.size() / 2);
	for (int ndx = 0; ndx < (int)currentPoints.size(); ++ndx)
	{
		currentPoints[ndx].center	= m_attribData[ndx*2].swizzle(0, 1);
		currentPoints[ndx].even		= (m_attribData[ndx*2 + 1].y() == 1.0f); // is green
		currentPoints[ndx].size		= ((m_isWidePointCase) ? ((currentPoints[ndx].even) ? 5 : 3) : 1);
	}

	// tessellation
	if (m_hasTessellationStage)
	{
		std::vector<GeneratedPoint> tessellatedPoints;

		tessellatedPoints.resize(currentPoints.size() * 4);
		for (int ndx = 0; ndx < (int)currentPoints.size(); ++ndx)
		{
			const tcu::Vec2 position = tcu::Vec2(currentPoints[ndx].center.x(), 1.0f - currentPoints[ndx].center.y()); // mirror Y

			tessellatedPoints[4 * ndx + 0].center	= position + tcu::Vec2(-0.07f, -0.07f);
			tessellatedPoints[4 * ndx + 0].size		= currentPoints[ndx].size;
			tessellatedPoints[4 * ndx + 0].even		= currentPoints[ndx].even;

			tessellatedPoints[4 * ndx + 1].center	= position + tcu::Vec2( 0.07f, -0.07f);
			tessellatedPoints[4 * ndx + 1].size		= currentPoints[ndx].size;
			tessellatedPoints[4 * ndx + 1].even		= currentPoints[ndx].even;

			tessellatedPoints[4 * ndx + 2].center	= position + tcu::Vec2( 0.07f,  0.07f);
			tessellatedPoints[4 * ndx + 2].size		= currentPoints[ndx].size;
			tessellatedPoints[4 * ndx + 2].even		= currentPoints[ndx].even;

			tessellatedPoints[4 * ndx + 3].center	= position + tcu::Vec2(-0.07f,  0.07f);
			tessellatedPoints[4 * ndx + 3].size		= currentPoints[ndx].size;
			tessellatedPoints[4 * ndx + 3].even		= currentPoints[ndx].even;
		}

		currentPoints.swap(tessellatedPoints);
	}

	// geometry
	if (m_hasGeometryStage)
	{
		std::vector<GeneratedPoint> geometryShadedPoints;

		geometryShadedPoints.resize(currentPoints.size() * 3);
		for (int ndx = 0; ndx < (int)currentPoints.size(); ++ndx)
		{
			const tcu::Vec2 position = tcu::Vec2(1.0f - currentPoints[ndx].center.x(), currentPoints[ndx].center.y()); // mirror X

			geometryShadedPoints[3 * ndx + 0].center	= position + tcu::Vec2( 0.05f,  0.03f);
			geometryShadedPoints[3 * ndx + 0].size		= currentPoints[ndx].size;
			geometryShadedPoints[3 * ndx + 0].even		= currentPoints[ndx].even;

			geometryShadedPoints[3 * ndx + 1].center	= position + tcu::Vec2(-0.01f, -0.02f);
			geometryShadedPoints[3 * ndx + 1].size		= currentPoints[ndx].size;
			geometryShadedPoints[3 * ndx + 1].even		= currentPoints[ndx].even;

			geometryShadedPoints[3 * ndx + 2].center	= position + tcu::Vec2(-0.05f,  0.02f);
			geometryShadedPoints[3 * ndx + 2].size		= currentPoints[ndx].size;
			geometryShadedPoints[3 * ndx + 2].even		= currentPoints[ndx].even;
		}

		currentPoints.swap(geometryShadedPoints);
	}

	// sort from left to right, top to bottom
	std::sort(currentPoints.begin(), currentPoints.end(), PointSorter());

	// map to pattern space
	for (int ndx = 0; ndx < (int)currentPoints.size(); ++ndx)
		currentPoints[ndx].center = currentPoints[ndx].center * config.patternSize + config.patternPos;

	currentPoints.swap(data);
}

bool PointRenderCase::verifyNarrowPointPattern (const tcu::Surface& viewport, const std::vector<GeneratedPoint>& refPoints, const ProjectedBBox& bbox, int& logFloodCounter)
{
	bool anyError = false;

	// check that there is something near each sample
	for (int pointNdx = 0; pointNdx < (int)refPoints.size(); ++pointNdx)
	{
		const float				epsilon		= 1.0e-6f;
		const GeneratedPoint&	refPoint	= refPoints[pointNdx];

		// skip points not in the the bbox, treat boundary as "in"
		if (refPoint.center.x() < bbox.min.x() - epsilon ||
			refPoint.center.y() < bbox.min.y() - epsilon ||
			refPoint.center.x() > bbox.max.x() + epsilon ||
			refPoint.center.y() > bbox.max.y() + epsilon)
			continue;
		else
		{
			// transform to viewport coords
			const tcu::IVec2 pixelCenter(deRoundFloatToInt32((refPoint.center.x() * 0.5f + 0.5f) * (float)viewport.getWidth()),
										 deRoundFloatToInt32((refPoint.center.y() * 0.5f + 0.5f) * (float)viewport.getHeight()));

			// find rasterized point in the result
			if (pixelCenter.x() < 1 || pixelCenter.y() < 1 || pixelCenter.x() >= viewport.getWidth()-1 || pixelCenter.y() >= viewport.getHeight()-1)
			{
				// viewport boundary, assume point is fine
			}
			else
			{
				const int	componentNdx	= (refPoint.even) ? (1) : (2); // analyze either green or blue channel
				bool		foundResult		= false;

				// check neighborhood
				for (int dy = -1; dy < 2 && !foundResult; ++dy)
				for (int dx = -1; dx < 2 && !foundResult; ++dx)
				{
					const tcu::IVec2	testPos	(pixelCenter.x() + dx, pixelCenter.y() + dy);
					const tcu::RGBA		color	= viewport.getPixel(testPos.x(), testPos.y());

					if (color.toIVec()[componentNdx] > 0)
						foundResult = true;
				}

				if (!foundResult)
				{
					anyError = true;

					if (--logFloodCounter >= 0)
					{
						m_testCtx.getLog()
							<< tcu::TestLog::Message
							<< "Missing point near " << pixelCenter << ", vertex coordinates=" << refPoint.center.swizzle(0, 1) << "."
							<< tcu::TestLog::EndMessage;
					}
				}
			}
		}
	}

	return anyError;
}

bool PointRenderCase::verifyWidePointPattern (const tcu::Surface& viewport, const std::vector<GeneratedPoint>& refPoints, const ProjectedBBox& bbox, int& logFloodCounter)
{
	bool anyError = false;

	// check that there is something near each sample
	for (int pointNdx = 0; pointNdx < (int)refPoints.size(); ++pointNdx)
	{
		const GeneratedPoint& refPoint = refPoints[pointNdx];

		if (refPoint.center.x() >= bbox.min.x() &&
			refPoint.center.y() >= bbox.min.y() &&
			refPoint.center.x() <= bbox.max.x() &&
			refPoint.center.y() <= bbox.max.y())
		{
			// point fully in the bounding box
			anyError |= !verifyWidePoint(viewport, refPoint, bbox, POINT_FULL, logFloodCounter);
		}
		else if (refPoint.center.x() >= bbox.min.x() + (float)refPoint.size / 2.0f &&
				 refPoint.center.y() >= bbox.min.y() - (float)refPoint.size / 2.0f &&
				 refPoint.center.x() <= bbox.max.x() + (float)refPoint.size / 2.0f &&
				 refPoint.center.y() <= bbox.max.y() - (float)refPoint.size / 2.0f)
		{
			// point leaks into bounding box
			anyError |= !verifyWidePoint(viewport, refPoint, bbox, POINT_PARTIAL, logFloodCounter);
		}
	}

	return anyError;
}

bool PointRenderCase::verifyWidePoint (const tcu::Surface& viewport, const GeneratedPoint& refPoint, const ProjectedBBox& bbox, ResultPointType pointType, int& logFloodCounter)
{
	const int			componentNdx		= (refPoint.even) ? (1) : (2);
	const int			halfPointSizeCeil	= (refPoint.size + 1) / 2;
	const int			halfPointSizeFloor	= (refPoint.size + 1) / 2;
	const tcu::IVec4	viewportBBoxArea	= getViewportBoundingBoxArea(bbox, tcu::IVec2(viewport.getWidth(), viewport.getHeight()), (float)refPoint.size);
	const tcu::IVec4	verificationArea	= tcu::IVec4(de::max(viewportBBoxArea.x(), 0),
														 de::max(viewportBBoxArea.y(), 0),
														 de::min(viewportBBoxArea.z(), viewport.getWidth()),
														 de::min(viewportBBoxArea.w(), viewport.getHeight()));
	const tcu::IVec2	pointPos			= tcu::IVec2(deRoundFloatToInt32((refPoint.center.x()*0.5f + 0.5f) * (float)viewport.getWidth()),
														 deRoundFloatToInt32((refPoint.center.y()*0.5f + 0.5f) * (float)viewport.getHeight()));

	// find any fragment within the point that is inside the bbox, start search at the center

	if (pointPos.x() >= verificationArea.x() &&
		pointPos.y() >= verificationArea.y() &&
		pointPos.x() < verificationArea.z() &&
		pointPos.y() < verificationArea.w())
	{
		if (viewport.getPixel(pointPos.x(), pointPos.y()).toIVec()[componentNdx])
			return verifyWidePointAt(pointPos, viewport, refPoint, verificationArea, pointType, componentNdx, logFloodCounter);
	}

	for (int dy = -halfPointSizeCeil; dy <= halfPointSizeCeil; ++dy)
	for (int dx = -halfPointSizeCeil; dx <= halfPointSizeCeil; ++dx)
	{
		const tcu::IVec2 testPos = pointPos + tcu::IVec2(dx, dy);

		if (dx == 0 && dy == 0)
			continue;

		if (testPos.x() >= verificationArea.x() &&
			testPos.y() >= verificationArea.y() &&
			testPos.x() < verificationArea.z() &&
			testPos.y() < verificationArea.w())
		{
			if (viewport.getPixel(testPos.x(), testPos.y()).toIVec()[componentNdx])
				return verifyWidePointAt(testPos, viewport, refPoint, verificationArea, pointType, componentNdx, logFloodCounter);
		}
	}

	// could not find point, this is only ok near boundaries
	if (pointPos.x() + halfPointSizeFloor <  verificationArea.x() - 1 ||
		pointPos.y() + halfPointSizeFloor <  verificationArea.y() - 1 ||
		pointPos.x() - halfPointSizeFloor >= verificationArea.z() - 1 ||
		pointPos.y() - halfPointSizeFloor >= verificationArea.w() - 1)
		return true;

	if (--logFloodCounter >= 0)
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Missing wide point near " << pointPos << ", vertex coordinates=" << refPoint.center.swizzle(0, 1) << "."
			<< tcu::TestLog::EndMessage;
	}

	return false;
}

bool PointRenderCase::verifyWidePointAt (const tcu::IVec2& pointPos, const tcu::Surface& viewport, const GeneratedPoint& refPoint, const tcu::IVec4& bbox, ResultPointType pointType, int componentNdx, int& logFloodCounter)
{
	const int				expectedPointSize		= refPoint.size;
	bool					viewportClippedTop		= false;
	bool					viewportClippedBottom	= false;
	bool					primitiveClippedTop		= false;
	bool					primitiveClippedBottom	= false;
	std::vector<tcu::IVec2>	widthsUpwards;
	std::vector<tcu::IVec2>	widthsDownwards;
	std::vector<tcu::IVec2>	widths;

	// search upwards
	for (int y = pointPos.y();; --y)
	{
		if (y < bbox.y() || y < 0)
		{
			if (y < bbox.y())
				primitiveClippedTop = true;
			if (y < 0)
				viewportClippedTop = true;
			break;
		}
		else if (pointPos.y() - y > expectedPointSize)
		{
			// no need to go further than point height
			break;
		}
		else if (viewport.getPixel(pointPos.x(), y).toIVec()[componentNdx] == 0)
		{
			break;
		}
		else
		{
			widthsUpwards.push_back(scanPointWidthAt(tcu::IVec2(pointPos.x(), y), viewport, expectedPointSize, componentNdx));
		}
	}

	// top is clipped
	if ((viewportClippedTop || (pointType == POINT_PARTIAL && primitiveClippedTop)) && !widthsUpwards.empty())
	{
		const tcu::IVec2&	range			= widthsUpwards.back();
		const bool			squareFits		= (range.y() - range.x() + 1) >= expectedPointSize;
		const bool			widthClipped	= (pointType == POINT_PARTIAL) && (range.x() <= bbox.x() || range.y() >= bbox.z());

		if (squareFits || widthClipped)
			return true;
	}

	// and downwards
	for (int y = pointPos.y()+1;; ++y)
	{
		if (y >= bbox.w() || y >= viewport.getHeight())
		{
			if (y >= bbox.w())
				primitiveClippedBottom = true;
			if (y >= viewport.getHeight())
				viewportClippedBottom = true;
			break;
		}
		else if (y - pointPos.y() > expectedPointSize)
		{
			// no need to go further than point height
			break;
		}
		else if (viewport.getPixel(pointPos.x(), y).toIVec()[componentNdx] == 0)
		{
			break;
		}
		else
		{
			widthsDownwards.push_back(scanPointWidthAt(tcu::IVec2(pointPos.x(), y), viewport, expectedPointSize, componentNdx));
		}
	}

	// bottom is clipped
	if ((viewportClippedBottom || (pointType == POINT_PARTIAL && primitiveClippedBottom)) && !(widthsDownwards.empty() && widthsUpwards.empty()))
	{
		const tcu::IVec2&	range			= (widthsDownwards.empty()) ? (widthsUpwards.front()) : (widthsDownwards.back());
		const bool			squareFits		= (range.y() - range.x() + 1) >= expectedPointSize;
		const bool			bboxClipped		= (pointType == POINT_PARTIAL) && (range.x() <= bbox.x() || range.y() >= bbox.z()-1);
		const bool			viewportClipped	= range.x() <= 0 || range.y() >= viewport.getWidth()-1;

		if (squareFits || bboxClipped || viewportClipped)
			return true;
	}

	// would square point would fit into the rasterized area

	for (int ndx = 0; ndx < (int)widthsUpwards.size(); ++ndx)
		widths.push_back(widthsUpwards[(int)widthsUpwards.size() - ndx - 1]);
	for (int ndx = 0; ndx < (int)widthsDownwards.size(); ++ndx)
		widths.push_back(widthsDownwards[ndx]);
	DE_ASSERT(!widths.empty());

	for (int y = 0; y < (int)widths.size() - expectedPointSize + 1; ++y)
	{
		tcu::IVec2 unionRange = widths[y];

		for (int dy = 1; dy < expectedPointSize; ++dy)
		{
			unionRange.x() = de::max(unionRange.x(), widths[y+dy].x());
			unionRange.y() = de::min(unionRange.y(), widths[y+dy].y());
		}

		// would a N x N block fit here?
		{
			const bool squareFits		= (unionRange.y() - unionRange.x() + 1) >= expectedPointSize;
			const bool bboxClipped		= (pointType == POINT_PARTIAL) && (unionRange.x() <= bbox.x() || unionRange.y() >= bbox.z()-1);
			const bool viewportClipped	= unionRange.x() <= 0 || unionRange.y() >= viewport.getWidth()-1;

			if (squareFits || bboxClipped || viewportClipped)
				return true;
		}
	}

	if (--logFloodCounter >= 0)
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Missing " << expectedPointSize << "x" << expectedPointSize << " point near " << pointPos << ", vertex coordinates=" << refPoint.center.swizzle(0, 1) << "."
			<< tcu::TestLog::EndMessage;
	}
	return false;
}

tcu::IVec2 PointRenderCase::scanPointWidthAt (const tcu::IVec2& pointPos, const tcu::Surface& viewport, int expectedPointSize, int componentNdx) const
{
	int minX = pointPos.x();
	int maxX = pointPos.x();

	// search horizontally for a point edges
	for (int x = pointPos.x()-1; x >= 0; --x)
	{
		if (viewport.getPixel(x, pointPos.y()).toIVec()[componentNdx] == 0)
			break;

		// no need to go further than point width
		if (pointPos.x() - x > expectedPointSize)
			break;

		minX = x;
	}
	for (int x = pointPos.x()+1; x < viewport.getWidth(); ++x)
	{
		if (viewport.getPixel(x, pointPos.y()).toIVec()[componentNdx] == 0)
			break;

		// no need to go further than point width
		if (x - pointPos.x() > expectedPointSize)
			break;

		maxX = x;
	}

	return tcu::IVec2(minX, maxX);
}

class BlitFboCase : public TestCase
{
public:
	enum RenderTarget
	{
		TARGET_DEFAULT = 0,
		TARGET_FBO,

		TARGET_LAST
	};

							BlitFboCase						(Context& context, const char* name, const char* description, RenderTarget src, RenderTarget dst);
							~BlitFboCase					(void);

private:
	enum
	{
		FBO_SIZE = 256,
	};

	struct BlitArgs
	{
		tcu::IVec4	src;
		tcu::IVec4	dst;
		tcu::Vec4	bboxMin;
		tcu::Vec4	bboxMax;
		bool		linear;
	};

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

	void							fillSourceWithPattern	(void);
	bool							verifyImage				(const BlitArgs& args);

	const RenderTarget				m_src;
	const RenderTarget				m_dst;

	std::vector<BlitArgs>			m_iterations;
	int								m_iteration;
	de::MovePtr<glu::Framebuffer>	m_srcFbo;
	de::MovePtr<glu::Framebuffer>	m_dstFbo;
	de::MovePtr<glu::Renderbuffer>	m_srcRbo;
	de::MovePtr<glu::Renderbuffer>	m_dstRbo;
	de::MovePtr<glu::ShaderProgram>	m_program;
	de::MovePtr<glu::Buffer>		m_vbo;
};

BlitFboCase::BlitFboCase (Context& context, const char* name, const char* description, RenderTarget src, RenderTarget dst)
	: TestCase		(context, name, description)
	, m_src			(src)
	, m_dst			(dst)
	, m_iteration	(0)
{
	DE_ASSERT(src < TARGET_LAST);
	DE_ASSERT(dst < TARGET_LAST);
}

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

void BlitFboCase::init (void)
{
	const int				numIterations			= 12;
	const bool				defaultFBMultisampled	= (m_context.getRenderTarget().getNumSamples() > 1);
	const glw::Functions&	gl						= m_context.getRenderContext().getFunctions();
	de::Random				rnd						(0xABC123);

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Using BlitFramebuffer to blit area from "
			<< ((m_src == TARGET_DEFAULT) ? ("default fb") : ("fbo"))
			<< " to "
			<< ((m_dst == TARGET_DEFAULT) ? ("default fb") : ("fbo"))
			<< ".\n"
		<< "Varying blit arguments and primitive bounding box between iterations.\n"
		<< "Expecting bounding box to have no effect on blitting.\n"
		<< "Source framebuffer is filled with green-yellow grid.\n"
		<< tcu::TestLog::EndMessage;

	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_primitive_bounding_box"))
		throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");
	if (m_dst == TARGET_DEFAULT && defaultFBMultisampled)
		throw tcu::NotSupportedError("Test requires non-multisampled default framebuffer");

	// resources

	if (m_src == TARGET_FBO)
	{
		m_srcRbo = de::MovePtr<glu::Renderbuffer>(new glu::Renderbuffer(m_context.getRenderContext()));
		gl.bindRenderbuffer(GL_RENDERBUFFER, **m_srcRbo);
		gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, FBO_SIZE, FBO_SIZE);
		GLU_EXPECT_NO_ERROR(gl.getError(), "src rbo");

		m_srcFbo = de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(m_context.getRenderContext()));
		gl.bindFramebuffer(GL_FRAMEBUFFER, **m_srcFbo);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, **m_srcRbo);
		GLU_EXPECT_NO_ERROR(gl.getError(), "src fbo");
	}

	if (m_dst == TARGET_FBO)
	{
		m_dstRbo = de::MovePtr<glu::Renderbuffer>(new glu::Renderbuffer(m_context.getRenderContext()));
		gl.bindRenderbuffer(GL_RENDERBUFFER, **m_dstRbo);
		gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, FBO_SIZE, FBO_SIZE);
		GLU_EXPECT_NO_ERROR(gl.getError(), "dst rbo");

		m_dstFbo = de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(m_context.getRenderContext()));
		gl.bindFramebuffer(GL_FRAMEBUFFER, **m_dstFbo);
		gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, **m_dstRbo);
		GLU_EXPECT_NO_ERROR(gl.getError(), "dst fbo");
	}

	{
		static const char* const s_vertexSource =	"#version 310 es\n"
													"in highp vec4 a_position;\n"
													"out highp vec4 v_position;\n"
													"void main()\n"
													"{\n"
													"	gl_Position = a_position;\n"
													"	v_position = a_position;\n"
													"}\n";
		static const char* const s_fragmentSource =	"#version 310 es\n"
													"in mediump vec4 v_position;\n"
													"layout(location=0) out mediump vec4 dEQP_FragColor;\n"
													"void main()\n"
													"{\n"
													"	const mediump vec4 green = vec4(0.0, 1.0, 0.0, 1.0);\n"
													"	const mediump vec4 yellow = vec4(1.0, 1.0, 0.0, 1.0);\n"
													"	dEQP_FragColor = (step(0.1, mod(v_position.x, 0.2)) == step(0.1, mod(v_position.y, 0.2))) ? (green) : (yellow);\n"
													"}\n";

		m_program = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_vertexSource) << glu::FragmentSource(s_fragmentSource)));

		if (!m_program->isOk())
		{
			m_testCtx.getLog() << *m_program;
			throw tcu::TestError("failed to build program");
		}
	}

	{
		static const tcu::Vec4 s_quadCoords[] =
		{
			tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
			tcu::Vec4(-1.0f,  1.0f, 0.0f, 1.0f),
			tcu::Vec4( 1.0f, -1.0f, 0.0f, 1.0f),
			tcu::Vec4( 1.0f,  1.0f, 0.0f, 1.0f),
		};

		m_vbo = de::MovePtr<glu::Buffer>(new glu::Buffer(m_context.getRenderContext()));

		gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
		gl.bufferData(GL_ARRAY_BUFFER, sizeof(s_quadCoords), s_quadCoords, GL_STATIC_DRAW);
		GLU_EXPECT_NO_ERROR(gl.getError(), "set buf");
	}

	// gen iterations

	{
		const tcu::IVec2 srcSize = (m_src == TARGET_DEFAULT) ? (tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight())) : (tcu::IVec2(FBO_SIZE, FBO_SIZE));
		const tcu::IVec2 dstSize = (m_dst == TARGET_DEFAULT) ? (tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight())) : (tcu::IVec2(FBO_SIZE, FBO_SIZE));

		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "srcSize = " << srcSize << "\n"
			<< "dstSize = " << dstSize << "\n"
			<< tcu::TestLog::EndMessage;

		for (int ndx = 0; ndx < numIterations; ++ndx)
		{
			BlitArgs args;

			if (m_src == TARGET_DEFAULT && defaultFBMultisampled)
			{
				const tcu::IVec2	unionSize	= tcu::IVec2(de::min(srcSize.x(), dstSize.x()), de::min(srcSize.y(), dstSize.y()));
				const int			srcWidth	= rnd.getInt(1, unionSize.x());
				const int			srcHeight	= rnd.getInt(1, unionSize.y());
				const int			srcX		= rnd.getInt(0, unionSize.x() - srcWidth);
				const int			srcY		= rnd.getInt(0, unionSize.y() - srcHeight);

				args.src.x() = srcX;
				args.src.y() = srcY;
				args.src.z() = srcX + srcWidth;
				args.src.w() = srcY + srcHeight;

				args.dst = args.src;
			}
			else
			{
				const int	srcWidth	= rnd.getInt(1, srcSize.x());
				const int	srcHeight	= rnd.getInt(1, srcSize.y());
				const int	srcX		= rnd.getInt(0, srcSize.x() - srcWidth);
				const int	srcY		= rnd.getInt(0, srcSize.y() - srcHeight);
				const int	dstWidth	= rnd.getInt(1, dstSize.x());
				const int	dstHeight	= rnd.getInt(1, dstSize.y());
				const int	dstX		= rnd.getInt(-(dstWidth / 2), dstSize.x() - (dstWidth+1) / 2);		// allow dst go out of bounds
				const int	dstY		= rnd.getInt(-(dstHeight / 2), dstSize.y() - (dstHeight+1)  / 2);

				args.src.x() = srcX;
				args.src.y() = srcY;
				args.src.z() = srcX + srcWidth;
				args.src.w() = srcY + srcHeight;
				args.dst.x() = dstX;
				args.dst.y() = dstY;
				args.dst.z() = dstX + dstWidth;
				args.dst.w() = dstY + dstHeight;
			}

			args.bboxMin.x() = rnd.getFloat(-1.1f, 1.1f);
			args.bboxMin.y() = rnd.getFloat(-1.1f, 1.1f);
			args.bboxMin.z() = rnd.getFloat(-1.1f, 1.1f);
			args.bboxMin.w() = rnd.getFloat( 0.9f, 1.1f);

			args.bboxMax.x() = rnd.getFloat(-1.1f, 1.1f);
			args.bboxMax.y() = rnd.getFloat(-1.1f, 1.1f);
			args.bboxMax.z() = rnd.getFloat(-1.1f, 1.1f);
			args.bboxMax.w() = rnd.getFloat( 0.9f, 1.1f);

			if (args.bboxMin.x() / args.bboxMin.w() > args.bboxMax.x() / args.bboxMax.w())
				std::swap(args.bboxMin.x(), args.bboxMax.x());
			if (args.bboxMin.y() / args.bboxMin.w() > args.bboxMax.y() / args.bboxMax.w())
				std::swap(args.bboxMin.y(), args.bboxMax.y());
			if (args.bboxMin.z() / args.bboxMin.w() > args.bboxMax.z() / args.bboxMax.w())
				std::swap(args.bboxMin.z(), args.bboxMax.z());

			args.linear = rnd.getBool();

			m_iterations.push_back(args);
		}
	}
}

void BlitFboCase::deinit (void)
{
	m_srcFbo.clear();
	m_srcRbo.clear();
	m_dstFbo.clear();
	m_dstRbo.clear();
	m_program.clear();
	m_vbo.clear();
}

BlitFboCase::IterateResult BlitFboCase::iterate (void)
{
	const tcu::ScopedLogSection	section		(m_testCtx.getLog(), "Iteration" + de::toString(m_iteration), "Iteration " + de::toString(m_iteration+1) + " / " + de::toString((int)m_iterations.size()));
	const BlitArgs&				blitCfg		= m_iterations[m_iteration];
	const glw::Functions&		gl			= m_context.getRenderContext().getFunctions();

	if (m_iteration == 0)
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	// fill source with test pattern. Default fb must be filled for each iteration because contents might not survive the swap
	if (m_src == TARGET_DEFAULT || m_iteration == 0)
		fillSourceWithPattern();

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Set bounding box:\n"
		<< "\tmin:" << blitCfg.bboxMin << "\n"
		<< "\tmax:" << blitCfg.bboxMax << "\n"
		<< "Blit:\n"
		<<	"\tsrc: " << blitCfg.src << "\n"
		<<	"\tdst: " << blitCfg.dst << "\n"
		<<	"\tfilter: " << ((blitCfg.linear) ? ("linear") : ("nearest"))
		<< tcu::TestLog::EndMessage;

	gl.primitiveBoundingBox(blitCfg.bboxMin.x(), blitCfg.bboxMin.y(), blitCfg.bboxMin.z(), blitCfg.bboxMin.w(),
							blitCfg.bboxMax.x(), blitCfg.bboxMax.y(), blitCfg.bboxMax.z(), blitCfg.bboxMax.w());

	gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, (m_dst == TARGET_FBO) ? (**m_dstFbo) : (m_context.getRenderContext().getDefaultFramebuffer()));
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);

	gl.bindFramebuffer(GL_READ_FRAMEBUFFER, (m_src == TARGET_FBO) ? (**m_srcFbo) : (m_context.getRenderContext().getDefaultFramebuffer()));
	gl.blitFramebuffer(blitCfg.src.x(), blitCfg.src.y(), blitCfg.src.z(), blitCfg.src.w(),
					   blitCfg.dst.x(), blitCfg.dst.y(), blitCfg.dst.z(), blitCfg.dst.w(),
					   GL_COLOR_BUFFER_BIT,
					   ((blitCfg.linear) ? (GL_LINEAR) : (GL_NEAREST)));
	GLU_EXPECT_NO_ERROR(gl.getError(), "blit");

	if (!verifyImage(blitCfg))
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got unexpected blit result");

	return (++m_iteration == (int)m_iterations.size()) ? (STOP) : (CONTINUE);
}

bool BlitFboCase::verifyImage (const BlitArgs& args)
{
	const int				colorThreshold	= 4; //!< this test case is not about how color is preserved, allow almost anything
	const tcu::IVec2		dstSize			= (m_dst == TARGET_DEFAULT) ? (tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight())) : (tcu::IVec2(FBO_SIZE, FBO_SIZE));
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	tcu::Surface			viewport		(dstSize.x(), dstSize.y());
	tcu::Surface			errorMask		(dstSize.x(), dstSize.y());
	bool					anyError		= false;

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Verifying blit result"
		<< tcu::TestLog::EndMessage;

	gl.bindFramebuffer(GL_READ_FRAMEBUFFER, (m_dst == TARGET_FBO) ? (**m_dstFbo) : (m_context.getRenderContext().getDefaultFramebuffer()));
	glu::readPixels(m_context.getRenderContext(), 0, 0, viewport.getAccess());

	tcu::clear(errorMask.getAccess(), tcu::IVec4(0, 0, 0, 255));

	for (int y = 0; y < dstSize.y(); ++y)
	for (int x = 0; x < dstSize.x(); ++x)
	{
		const tcu::RGBA color	= viewport.getPixel(x, y);
		const bool		inside	= (x >= args.dst.x() && x < args.dst.z() && y >= args.dst.y() && y < args.dst.w());
		const bool		error	= (inside) ? (color.getGreen() < 255 - colorThreshold || color.getBlue() > colorThreshold)
										   : (color.getRed() > colorThreshold || color.getGreen() > colorThreshold || color.getBlue() > colorThreshold);

		if (error)
		{
			anyError = true;
			errorMask.setPixel(x, y, tcu::RGBA::red());
		}
	}

	if (anyError)
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Image verification failed."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Viewport", "Viewport contents", viewport.getAccess())
			<< tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
			<< tcu::TestLog::EndImageSet;
		return false;
	}
	else
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Result image ok."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Viewport", "Viewport contents", viewport.getAccess())
			<< tcu::TestLog::EndImageSet;
		return true;
	}
}

void BlitFboCase::fillSourceWithPattern (void)
{
	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
	const tcu::IVec2		srcSize		= (m_src == TARGET_DEFAULT) ? (tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight())) : (tcu::IVec2(FBO_SIZE, FBO_SIZE));
	const int				posLocation	= gl.getAttribLocation(m_program->getProgram(), "a_position");

	gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, (m_src == TARGET_FBO) ? (**m_srcFbo) : (m_context.getRenderContext().getDefaultFramebuffer()));
	gl.viewport(0, 0, srcSize.x(), srcSize.y());
	gl.useProgram(m_program->getProgram());

	gl.clearColor(0.0f, 0.0f, 1.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);

	gl.enableVertexAttribArray(posLocation);
	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 4 * (int)sizeof(float), NULL);
	gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
	GLU_EXPECT_NO_ERROR(gl.getError(), "draw");
}

class DepthDrawCase : public TestCase
{
public:
	enum DepthType
	{
		DEPTH_BUILTIN = 0,
		DEPTH_USER_DEFINED,

		DEPTH_LAST
	};
	enum BBoxState
	{
		STATE_GLOBAL = 0,
		STATE_PER_PRIMITIVE,

		STATE_LAST
	};
	enum BBoxSize
	{
		BBOX_EQUAL = 0,
		BBOX_LARGER,

		BBOX_LAST
	};

									DepthDrawCase					(Context& context, const char* name, const char* description, DepthType depthType, BBoxState state, BBoxSize bboxSize);
									~DepthDrawCase					(void);

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

	std::string						genVertexSource					(void) const;
	std::string						genFragmentSource				(void) const;
	std::string						genTessellationControlSource	(void) const;
	std::string						genTessellationEvaluationSource	(void) const;
	void							generateAttributeData			(std::vector<tcu::Vec4>& data) const;
	bool							verifyImage						(const tcu::Surface& viewport) const;

	enum
	{
		RENDER_AREA_SIZE = 256,
	};

	struct LayerInfo
	{
		float		zOffset;
		float		zScale;
		tcu::Vec4	color1;
		tcu::Vec4	color2;
	};

	const int						m_numLayers;
	const int						m_gridSize;

	const DepthType					m_depthType;
	const BBoxState					m_state;
	const BBoxSize					m_bboxSize;

	de::MovePtr<glu::ShaderProgram>	m_program;
	de::MovePtr<glu::Buffer>		m_vbo;
	std::vector<LayerInfo>			m_layers;
};

DepthDrawCase::DepthDrawCase (Context& context, const char* name, const char* description, DepthType depthType, BBoxState state, BBoxSize bboxSize)
	: TestCase		(context, name, description)
	, m_numLayers	(14)
	, m_gridSize	(24)
	, m_depthType	(depthType)
	, m_state		(state)
	, m_bboxSize	(bboxSize)
{
	DE_ASSERT(depthType < DEPTH_LAST);
	DE_ASSERT(state < STATE_LAST);
	DE_ASSERT(bboxSize < BBOX_LAST);
}

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

void DepthDrawCase::init (void)
{
	const glw::Functions& gl = m_context.getRenderContext().getFunctions();

	// requirements

	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_primitive_bounding_box"))
		throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");
	if (m_state == STATE_PER_PRIMITIVE && !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
		throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");
	if (m_context.getRenderTarget().getDepthBits() == 0)
		throw tcu::NotSupportedError("Test requires depth buffer");
	if (m_context.getRenderTarget().getWidth() < RENDER_AREA_SIZE || m_context.getRenderTarget().getHeight() < RENDER_AREA_SIZE)
		throw tcu::NotSupportedError("Test requires " + de::toString<int>(RENDER_AREA_SIZE) + "x" + de::toString<int>(RENDER_AREA_SIZE) + " viewport");

	// log
	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Rendering multiple triangle grids with with different z coordinates.\n"
		<< "Topmost grid is green-yellow, other grids are blue-red.\n"
		<< "Expecting only the green-yellow grid to be visible.\n"
		<< "Setting primitive bounding box "
			<< ((m_bboxSize == BBOX_EQUAL) ? ("to exactly cover") : ("to cover"))
			<< ((m_state == STATE_GLOBAL) ? (" each grid") : (" each triangle"))
			<< ((m_bboxSize == BBOX_EQUAL) ? (".") : (" and include some padding."))
			<< "\n"
		<< "Set bounding box using "
			<< ((m_state == STATE_GLOBAL) ? ("PRIMITIVE_BOUNDING_BOX_EXT state") : ("gl_BoundingBoxEXT output"))
			<< "\n"
		<< ((m_depthType == DEPTH_USER_DEFINED) ? ("Fragment depth is set in the fragment shader") : (""))
		<< tcu::TestLog::EndMessage;

	// resources

	{
		glu::ProgramSources sources;
		sources << glu::VertexSource(genVertexSource());
		sources << glu::FragmentSource(genFragmentSource());

		if (m_state == STATE_PER_PRIMITIVE)
			sources << glu::TessellationControlSource(genTessellationControlSource())
					<< glu::TessellationEvaluationSource(genTessellationEvaluationSource());

		m_program = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(), sources));
		GLU_EXPECT_NO_ERROR(gl.getError(), "build program");

		{
			const tcu::ScopedLogSection section(m_testCtx.getLog(), "ShaderProgram", "Shader program");
			m_testCtx.getLog() << *m_program;
		}

		if (!m_program->isOk())
			throw tcu::TestError("failed to build program");
	}

	{
		std::vector<tcu::Vec4> data;

		generateAttributeData(data);

		m_vbo = de::MovePtr<glu::Buffer>(new glu::Buffer(m_context.getRenderContext()));
		gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
		gl.bufferData(GL_ARRAY_BUFFER, (int)(sizeof(tcu::Vec4) * data.size()), &data[0], GL_STATIC_DRAW);
		GLU_EXPECT_NO_ERROR(gl.getError(), "buf upload");
	}

	// gen layers
	{
		de::Random rnd(0x12345);

		m_layers.resize(m_numLayers);
		for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx)
		{
			m_layers[layerNdx].zOffset	= ((float)layerNdx / (float)m_numLayers) * 2.0f - 1.0f;
			m_layers[layerNdx].zScale	= (2.0f / (float)m_numLayers);
			m_layers[layerNdx].color1	= (layerNdx == 0) ? (tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f)) : (tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f));
			m_layers[layerNdx].color2	= (layerNdx == 0) ? (tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f)) : (tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f));
		}
		rnd.shuffle(m_layers.begin(), m_layers.end());
	}
}

void DepthDrawCase::deinit (void)
{
	m_program.clear();
	m_vbo.clear();
}

DepthDrawCase::IterateResult DepthDrawCase::iterate (void)
{
	const bool				hasTessellation		= (m_state == STATE_PER_PRIMITIVE);
	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	const glw::GLint		posLocation			= gl.getAttribLocation(m_program->getProgram(), "a_position");
	const glw::GLint		colLocation			= gl.getAttribLocation(m_program->getProgram(), "a_colorMix");
	const glw::GLint		depthBiasLocation	= gl.getUniformLocation(m_program->getProgram(), "u_depthBias");
	const glw::GLint		depthScaleLocation	= gl.getUniformLocation(m_program->getProgram(), "u_depthScale");
	const glw::GLint		color1Location		= gl.getUniformLocation(m_program->getProgram(), "u_color1");
	const glw::GLint		color2Location		= gl.getUniformLocation(m_program->getProgram(), "u_color2");

	tcu::Surface			viewport			(RENDER_AREA_SIZE, RENDER_AREA_SIZE);
	de::Random				rnd					(0x213237);

	TCU_CHECK(posLocation != -1);
	TCU_CHECK(colLocation != -1);
	TCU_CHECK(depthBiasLocation != -1);
	TCU_CHECK(depthScaleLocation != -1);
	TCU_CHECK(color1Location != -1);
	TCU_CHECK(color2Location != -1);

	gl.viewport(0, 0, RENDER_AREA_SIZE, RENDER_AREA_SIZE);
	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clearDepthf(1.0f);
	gl.depthFunc(GL_LESS);
	gl.enable(GL_DEPTH_TEST);
	gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	GLU_EXPECT_NO_ERROR(gl.getError(), "setup viewport");

	gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, (int)(8 * sizeof(float)), (const float*)DE_NULL);
	gl.vertexAttribPointer(colLocation, 4, GL_FLOAT, GL_FALSE, (int)(8 * sizeof(float)), (const float*)DE_NULL + 4);
	gl.enableVertexAttribArray(posLocation);
	gl.enableVertexAttribArray(colLocation);
	gl.useProgram(m_program->getProgram());
	GLU_EXPECT_NO_ERROR(gl.getError(), "setup va");

	if (hasTessellation)
		gl.patchParameteri(GL_PATCH_VERTICES, 3);

	for (int layerNdx = 0; layerNdx < m_numLayers; ++layerNdx)
	{
		gl.uniform1f(depthBiasLocation, m_layers[layerNdx].zOffset);
		gl.uniform1f(depthScaleLocation, m_layers[layerNdx].zScale);
		gl.uniform4fv(color1Location, 1, m_layers[layerNdx].color1.getPtr());
		gl.uniform4fv(color2Location, 1, m_layers[layerNdx].color2.getPtr());

		if (m_state == STATE_GLOBAL)
		{
			const float negPadding = (m_bboxSize == BBOX_EQUAL) ? (0.0f) : (rnd.getFloat() * 0.3f);
			const float posPadding = (m_bboxSize == BBOX_EQUAL) ? (0.0f) : (rnd.getFloat() * 0.3f);

			gl.primitiveBoundingBox(-1.0f, -1.0f, m_layers[layerNdx].zOffset - negPadding, 1.0f,
									1.0f,  1.0f, (m_layers[layerNdx].zOffset + m_layers[layerNdx].zScale + posPadding), 1.0f);
		}

		gl.drawArrays((hasTessellation) ? (GL_PATCHES) : (GL_TRIANGLES), 0, m_gridSize * m_gridSize * 6);
	}

	glu::readPixels(m_context.getRenderContext(), 0, 0, viewport.getAccess());
	GLU_EXPECT_NO_ERROR(gl.getError(), "render and read");

	if (verifyImage(viewport))
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");

	return STOP;
}

std::string DepthDrawCase::genVertexSource (void) const
{
	const bool			hasTessellation	= (m_state == STATE_PER_PRIMITIVE);
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"in highp vec4 a_position;\n"
			"in highp vec4 a_colorMix;\n"
			"out highp vec4 vtx_colorMix;\n";

	if (!hasTessellation && m_depthType == DEPTH_USER_DEFINED)
		buf << "out highp float v_fragDepth;\n";

	if (!hasTessellation)
		buf <<	"uniform highp float u_depthBias;\n"
				"uniform highp float u_depthScale;\n";

	buf <<	"\n"
			"void main()\n"
			"{\n";

	if (hasTessellation)
		buf << "	gl_Position = a_position;\n";
	else if (m_depthType == DEPTH_USER_DEFINED)
		buf <<	"	highp float dummyZ = a_position.z;\n"
				"	highp float writtenZ = a_position.w;\n"
				"	gl_Position = vec4(a_position.xy, dummyZ, 1.0);\n"
				"	v_fragDepth = writtenZ * u_depthScale + u_depthBias;\n";
	else
		buf <<	"	highp float writtenZ = a_position.w;\n"
				"	gl_Position = vec4(a_position.xy, writtenZ * u_depthScale + u_depthBias, 1.0);\n";

	buf <<	"	vtx_colorMix = a_colorMix;\n"
			"}\n";

	return buf.str();
}

std::string DepthDrawCase::genFragmentSource (void) const
{
	const bool			hasTessellation	= (m_state == STATE_PER_PRIMITIVE);
	const char* const	colorMixName	= (hasTessellation) ? ("tess_eval_colorMix") : ("vtx_colorMix");
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"in mediump vec4 " << colorMixName << ";\n";

	if (m_depthType == DEPTH_USER_DEFINED)
		buf << "in mediump float v_fragDepth;\n";

	buf <<	"layout(location = 0) out mediump vec4 o_color;\n"
			"uniform highp vec4 u_color1;\n"
			"uniform highp vec4 u_color2;\n"
			"\n"
			"void main()\n"
			"{\n"
			"	o_color = mix(u_color1, u_color2, " << colorMixName << ");\n";

	if (m_depthType == DEPTH_USER_DEFINED)
		buf << "	gl_FragDepth = v_fragDepth * 0.5 + 0.5;\n";

	buf <<	"}\n";

	return buf.str();
}

std::string DepthDrawCase::genTessellationControlSource (void) const
{
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			"#extension GL_EXT_primitive_bounding_box : require\n"
			"layout(vertices=3) out;\n"
			"\n"
			"uniform highp float u_depthBias;\n"
			"uniform highp float u_depthScale;\n"
			"\n"
			"in highp vec4 vtx_colorMix[];\n"
			"out highp vec4 tess_ctrl_colorMix[];\n"
			"\n"
			"void main()\n"
			"{\n"
			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
			"	tess_ctrl_colorMix[gl_InvocationID] = vtx_colorMix[0];\n"
			"\n"
			"	gl_TessLevelOuter[0] = 2.8;\n"
			"	gl_TessLevelOuter[1] = 2.8;\n"
			"	gl_TessLevelOuter[2] = 2.8;\n"
			"	gl_TessLevelInner[0] = 2.8;\n"
			"\n"
			"	// real Z stored in w component\n"
			"	highp vec4 minBound = vec4(min(min(vec3(gl_in[0].gl_Position.xy, gl_in[0].gl_Position.w * u_depthScale + u_depthBias),\n"
			"	                                   vec3(gl_in[1].gl_Position.xy, gl_in[1].gl_Position.w * u_depthScale + u_depthBias)),\n"
			"	                               vec3(gl_in[2].gl_Position.xy, gl_in[2].gl_Position.w * u_depthScale + u_depthBias)), 1.0);\n"
			"	highp vec4 maxBound = vec4(max(max(vec3(gl_in[0].gl_Position.xy, gl_in[0].gl_Position.w * u_depthScale + u_depthBias),\n"
			"	                                   vec3(gl_in[1].gl_Position.xy, gl_in[1].gl_Position.w * u_depthScale + u_depthBias)),\n"
			"	                               vec3(gl_in[2].gl_Position.xy, gl_in[2].gl_Position.w * u_depthScale + u_depthBias)), 1.0);\n";

	if (m_bboxSize == BBOX_EQUAL)
		buf <<	"	gl_BoundingBoxEXT[0] = minBound;\n"
				"	gl_BoundingBoxEXT[1] = maxBound;\n";
	else
		buf <<	"	highp float nedPadding = mod(gl_in[0].gl_Position.z, 0.3);\n"
				"	highp float posPadding = mod(gl_in[1].gl_Position.z, 0.3);\n"
				"	gl_BoundingBoxEXT[0] = minBound - vec4(0.0, 0.0, nedPadding, 0.0);\n"
				"	gl_BoundingBoxEXT[1] = maxBound + vec4(0.0, 0.0, posPadding, 0.0);\n";

	buf <<	"}\n";

	return buf.str();
}

std::string DepthDrawCase::genTessellationEvaluationSource (void) const
{
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			"#extension GL_EXT_gpu_shader5 : require\n"
			"layout(triangles) in;\n"
			"\n"
			"in highp vec4 tess_ctrl_colorMix[];\n"
			"out highp vec4 tess_eval_colorMix;\n";

	if (m_depthType == DEPTH_USER_DEFINED)
		buf << "out highp float v_fragDepth;\n";

	buf <<	"uniform highp float u_depthBias;\n"
			"uniform highp float u_depthScale;\n"
			"\n"
			"precise gl_Position;\n"
			"\n"
			"void main()\n"
			"{\n"
			"	highp vec4 tessellatedPos = gl_TessCoord.x * gl_in[0].gl_Position + gl_TessCoord.y * gl_in[1].gl_Position + gl_TessCoord.z * gl_in[2].gl_Position;\n";

	if (m_depthType == DEPTH_USER_DEFINED)
		buf <<	"	highp float dummyZ = tessellatedPos.z;\n"
				"	highp float writtenZ = tessellatedPos.w;\n"
				"	gl_Position = vec4(tessellatedPos.xy, dummyZ, 1.0);\n"
				"	v_fragDepth = writtenZ * u_depthScale + u_depthBias;\n";
	else
		buf <<	"	highp float writtenZ = tessellatedPos.w;\n"
				"	gl_Position = vec4(tessellatedPos.xy, writtenZ * u_depthScale + u_depthBias, 1.0);\n";

	buf <<	"	tess_eval_colorMix = tess_ctrl_colorMix[0];\n"
			"}\n";

	return buf.str();
}

void DepthDrawCase::generateAttributeData (std::vector<tcu::Vec4>& data) const
{
	const tcu::Vec4		color1				(0.0f, 0.0f, 0.0f, 0.0f); // mix weights
	const tcu::Vec4		color2				(1.0f, 1.0f, 1.0f, 1.0f);
	std::vector<int>	cellOrder			(m_gridSize * m_gridSize);
	de::Random			rnd					(0xAB54321);

	// generate grid with cells in random order
	for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
		cellOrder[ndx] = ndx;
	rnd.shuffle(cellOrder.begin(), cellOrder.end());

	data.resize(m_gridSize * m_gridSize * 6 * 2);
	for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
	{
		const int			cellNdx		= cellOrder[ndx];
		const int			cellX		= cellNdx % m_gridSize;
		const int			cellY		= cellNdx / m_gridSize;
		const tcu::Vec4&	cellColor	= ((cellX+cellY)%2 == 0) ? (color1) : (color2);

		data[ndx * 6 * 2 +  0] = tcu::Vec4(float(cellX+0) / float(m_gridSize) * 2.0f - 1.0f, float(cellY+0) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);	data[ndx * 6 * 2 +  1] = cellColor;
		data[ndx * 6 * 2 +  2] = tcu::Vec4(float(cellX+1) / float(m_gridSize) * 2.0f - 1.0f, float(cellY+1) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);	data[ndx * 6 * 2 +  3] = cellColor;
		data[ndx * 6 * 2 +  4] = tcu::Vec4(float(cellX+0) / float(m_gridSize) * 2.0f - 1.0f, float(cellY+1) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);	data[ndx * 6 * 2 +  5] = cellColor;
		data[ndx * 6 * 2 +  6] = tcu::Vec4(float(cellX+0) / float(m_gridSize) * 2.0f - 1.0f, float(cellY+0) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);	data[ndx * 6 * 2 +  7] = cellColor;
		data[ndx * 6 * 2 +  8] = tcu::Vec4(float(cellX+1) / float(m_gridSize) * 2.0f - 1.0f, float(cellY+0) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);	data[ndx * 6 * 2 +  9] = cellColor;
		data[ndx * 6 * 2 + 10] = tcu::Vec4(float(cellX+1) / float(m_gridSize) * 2.0f - 1.0f, float(cellY+1) / float(m_gridSize) * 2.0f - 1.0f, 0.0f, 0.0f);	data[ndx * 6 * 2 + 11] = cellColor;

		// Fill Z with random values (fake Z)
		for (int vtxNdx = 0; vtxNdx < 6; ++vtxNdx)
			data[ndx * 6 * 2 + 2*vtxNdx].z() = rnd.getFloat(0.0f, 1.0);

		// Fill W with other random values (written Z)
		for (int vtxNdx = 0; vtxNdx < 6; ++vtxNdx)
			data[ndx * 6 * 2 + 2*vtxNdx].w() = rnd.getFloat(0.0f, 1.0);
	}
}

bool DepthDrawCase::verifyImage (const tcu::Surface& viewport) const
{
	tcu::Surface	errorMask	(viewport.getWidth(), viewport.getHeight());
	bool			anyError	= false;

	tcu::clear(errorMask.getAccess(), tcu::IVec4(0,0,0,255));

	for (int y = 0; y < viewport.getHeight(); ++y)
	for (int x = 0; x < viewport.getWidth(); ++x)
	{
		const tcu::RGBA	pixel		= viewport.getPixel(x, y);
		bool			error		= false;

		// expect green, yellow or a combination of these
		if (pixel.getGreen() != 255 || pixel.getBlue() != 0)
			error = true;

		if (error)
		{
			errorMask.setPixel(x, y, tcu::RGBA::red());
			anyError = true;
		}
	}

	if (anyError)
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Image verification failed."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Viewport", "Viewport contents", viewport.getAccess())
			<< tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
			<< tcu::TestLog::EndImageSet;
	else
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Result image ok."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Viewport", "Viewport contents", viewport.getAccess())
			<< tcu::TestLog::EndImageSet;

	return !anyError;
}

class ClearCase : public TestCase
{
public:
	enum
	{
		SCISSOR_CLEAR_BIT		= 1 << 0,
		DRAW_TRIANGLE_BIT		= 1 << 1,
		PER_PRIMITIVE_BBOX_BIT	= 1 << 2,
		FULLSCREEN_SCISSOR_BIT	= 1 << 3,
	};

									ClearCase						(Context& context, const char* name, const char* description, deUint32 flags);
									~ClearCase						(void);

private:
	struct DrawObject
	{
		int firstNdx;
		int numVertices;
	};

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

	void							createVbo						(void);
	void							createProgram					(void);
	void							renderTo						(tcu::Surface& dst, bool useBBox);
	bool							verifyImagesEqual				(const tcu::PixelBufferAccess& withoutBBox, const tcu::PixelBufferAccess& withBBox);
	bool							verifyImageResultValid			(const tcu::PixelBufferAccess& result);

	std::string						genVertexSource					(void) const;
	std::string						genFragmentSource				(void) const;
	std::string						genTessellationControlSource	(bool setBBox) const;
	std::string						genTessellationEvaluationSource	(void) const;

	const bool						m_scissoredClear;
	const bool						m_fullscreenScissor;
	const bool						m_drawTriangles;
	const bool						m_useGlobalState;

	de::MovePtr<glu::Buffer>		m_vbo;
	de::MovePtr<glu::ShaderProgram>	m_perPrimitiveProgram;
	de::MovePtr<glu::ShaderProgram>	m_basicProgram;
	std::vector<DrawObject>			m_drawObjects;
	std::vector<tcu::Vec4>			m_objectVertices;
};

ClearCase::ClearCase (Context& context, const char* name, const char* description, deUint32 flags)
	: TestCase				(context, name, description)
	, m_scissoredClear		((flags & SCISSOR_CLEAR_BIT) != 0)
	, m_fullscreenScissor	((flags & FULLSCREEN_SCISSOR_BIT) != 0)
	, m_drawTriangles		((flags & DRAW_TRIANGLE_BIT) != 0)
	, m_useGlobalState		((flags & PER_PRIMITIVE_BBOX_BIT) == 0)
{
	DE_ASSERT(m_useGlobalState || m_drawTriangles); // per-triangle bbox requires triangles
	DE_ASSERT(!m_fullscreenScissor || m_scissoredClear); // fullscreenScissor requires scissoredClear
}

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

void ClearCase::init (void)
{
	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_primitive_bounding_box"))
		throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");
	if (m_drawTriangles && !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
		throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Doing multiple"
			<< ((m_scissoredClear) ? (" scissored") : (""))
			<< " color buffer clears"
			<< ((m_drawTriangles) ? (" and drawing some geometry between them") : (""))
			<< ".\n"
		<< ((m_scissoredClear && m_fullscreenScissor) ? ("Setting scissor area to cover entire viewport.\n") : (""))
		<< "Rendering with and without setting the bounding box.\n"
		<< "Expecting bounding box to have no effect on clears (i.e. results are constant).\n"
		<< "Set bounding box using "
			<< ((m_useGlobalState) ? ("PRIMITIVE_BOUNDING_BOX_EXT state") : ("gl_BoundingBoxEXT output"))
			<< ".\n"
		<< "Clear color is green with yellowish shades.\n"
		<< ((m_drawTriangles) ? ("Primitive color is yellow with greenish shades.\n") : (""))
		<< tcu::TestLog::EndMessage;

	if (m_drawTriangles)
	{
		createVbo();
		createProgram();
	}
}

void ClearCase::deinit (void)
{
	m_vbo.clear();
	m_perPrimitiveProgram.clear();
	m_basicProgram.clear();
	m_drawObjects = std::vector<DrawObject>();
	m_objectVertices = std::vector<tcu::Vec4>();
}

ClearCase::IterateResult ClearCase::iterate (void)
{
	const tcu::IVec2	renderTargetSize	(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
	tcu::Surface		resultWithoutBBox	(renderTargetSize.x(), renderTargetSize.y());
	tcu::Surface		resultWithBBox		(renderTargetSize.x(), renderTargetSize.y());

	// render with and without bbox set
	for (int passNdx = 0; passNdx < 2; ++passNdx)
	{
		const bool		useBBox			= (passNdx == 1);
		tcu::Surface&	destination		= (useBBox) ? (resultWithBBox) : (resultWithoutBBox);

		renderTo(destination, useBBox);
	}

	// Verify images are equal and that the image does not contain (trivially detectable) garbage

	if (!verifyImagesEqual(resultWithoutBBox.getAccess(), resultWithBBox.getAccess()))
	{
		// verifyImagesEqual will print out the image and error mask
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
	}
	else if (!verifyImageResultValid(resultWithBBox.getAccess()))
	{
		// verifyImageResultValid will print out the image and error mask
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result verification failed");
	}
	else
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Image comparison passed."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Result", "Result", resultWithBBox.getAccess())
			<< tcu::TestLog::EndImageSet;

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

	return STOP;
}

void ClearCase::createVbo (void)
{
	const int				numObjects	= 16;
	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
	de::Random				rnd			(deStringHash(getName()));

	m_vbo = de::MovePtr<glu::Buffer>(new glu::Buffer(m_context.getRenderContext()));

	for (int objectNdx = 0; objectNdx < numObjects; ++objectNdx)
	{
		const int	numTriangles	= rnd.getInt(1, 4);
		const float	minX			= rnd.getFloat(-1.2f, 0.8f);
		const float	minY			= rnd.getFloat(-1.2f, 0.8f);
		const float	maxX			= minX + rnd.getFloat(0.2f, 1.0f);
		const float	maxY			= minY + rnd.getFloat(0.2f, 1.0f);

		DrawObject	drawObject;
		drawObject.firstNdx = (int)m_objectVertices.size();
		drawObject.numVertices = numTriangles * 3;

		m_drawObjects.push_back(drawObject);

		for (int triangleNdx = 0; triangleNdx < numTriangles; ++triangleNdx)
		for (int vertexNdx = 0; vertexNdx < 3; ++vertexNdx)
		{
			const float posX = rnd.getFloat(minX, maxX);
			const float posY = rnd.getFloat(minY, maxY);
			const float posZ = rnd.getFloat(-0.7f, 0.7f);
			const float posW = rnd.getFloat(0.9f, 1.1f);

			m_objectVertices.push_back(tcu::Vec4(posX, posY, posZ, posW));
		}
	}

	gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
	gl.bufferData(GL_ARRAY_BUFFER, (int)(m_objectVertices.size() * sizeof(tcu::Vec4)), &m_objectVertices[0], GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR(gl.getError(), "buffer upload");
}

void ClearCase::createProgram (void)
{
	m_basicProgram = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(),
																			glu::ProgramSources()
																				<< glu::VertexSource(genVertexSource())
																				<< glu::FragmentSource(genFragmentSource())
																				<< glu::TessellationControlSource(genTessellationControlSource(false))
																				<< glu::TessellationEvaluationSource(genTessellationEvaluationSource())));

	m_testCtx.getLog()
		<< tcu::TestLog::Section("Program", "Shader program")
		<< *m_basicProgram
		<< tcu::TestLog::EndSection;

	if (!m_basicProgram->isOk())
		throw tcu::TestError("shader build failed");

	if (!m_useGlobalState)
	{
		m_perPrimitiveProgram = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(),
																					   glu::ProgramSources()
																							<< glu::VertexSource(genVertexSource())
																							<< glu::FragmentSource(genFragmentSource())
																							<< glu::TessellationControlSource(genTessellationControlSource(true))
																							<< glu::TessellationEvaluationSource(genTessellationEvaluationSource())));

		m_testCtx.getLog()
			<< tcu::TestLog::Section("PerPrimitiveProgram", "Shader program that sets the bounding box")
			<< *m_perPrimitiveProgram
			<< tcu::TestLog::EndSection;

		if (!m_perPrimitiveProgram->isOk())
			throw tcu::TestError("shader build failed");
	}
}

void ClearCase::renderTo (tcu::Surface& dst, bool useBBox)
{
	const int				numOps				= 45;
	const tcu::Vec4			yellow				(1.0f, 1.0f, 0.0f, 1.0f);
	const tcu::Vec4			green				(0.0f, 1.0f, 0.0f, 1.0f);
	const tcu::IVec2		renderTargetSize	(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	de::Random				rnd					(deStringHash(getName()));
	glu::VertexArray		vao					(m_context.getRenderContext());

	// always do the initial clear
	gl.disable(GL_SCISSOR_TEST);
	gl.viewport(0, 0, renderTargetSize.x(), renderTargetSize.y());
	gl.clearColor(yellow.x(), yellow.y(), yellow.z(), yellow.w());
	gl.clear(GL_COLOR_BUFFER_BIT);
	gl.finish();

	// prepare draw
	if (m_scissoredClear)
		gl.enable(GL_SCISSOR_TEST);

	if (m_drawTriangles)
	{
		const deUint32	programHandle		= (m_useGlobalState || !useBBox) ? (m_basicProgram->getProgram()) : (m_perPrimitiveProgram->getProgram());
		const int		positionAttribLoc	= gl.getAttribLocation(programHandle, "a_position");

		TCU_CHECK(positionAttribLoc != -1);

		gl.useProgram(programHandle);
		gl.bindVertexArray(*vao);
		gl.enableVertexAttribArray(positionAttribLoc);
		gl.vertexAttribPointer(positionAttribLoc, 4, GL_FLOAT, GL_FALSE, (int)sizeof(tcu::Vec4), DE_NULL);
		gl.patchParameteri(GL_PATCH_VERTICES, 3);
	}

	// do random scissor/clearldraw operations
	for (int opNdx = 0; opNdx < numOps; ++opNdx)
	{
		const int	drawObjNdx				= (m_drawTriangles) ? (rnd.getInt(0, (int)m_drawObjects.size() - 1)) : (0);
		const int	objectVertexStartNdx	= (m_drawTriangles) ? (m_drawObjects[drawObjNdx].firstNdx) : (0);
		const int	objectVertexLength		= (m_drawTriangles) ? (m_drawObjects[drawObjNdx].numVertices) : (0);
		tcu::Vec4	bboxMin;
		tcu::Vec4	bboxMax;

		if (m_drawTriangles)
		{
			bboxMin = tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f);
			bboxMax = tcu::Vec4(-1.0f, -1.0f, -1.0f, -1.0f);

			// calc bbox
			for (int vertexNdx = objectVertexStartNdx; vertexNdx < objectVertexStartNdx + objectVertexLength; ++vertexNdx)
			for (int componentNdx = 0; componentNdx < 4; ++componentNdx)
			{
				bboxMin[componentNdx] = de::min(bboxMin[componentNdx], m_objectVertices[vertexNdx][componentNdx]);
				bboxMax[componentNdx] = de::max(bboxMax[componentNdx], m_objectVertices[vertexNdx][componentNdx]);
			}
		}
		else
		{
			// no geometry, just random something
			bboxMin.x() = rnd.getFloat(-1.2f, 1.0f);
			bboxMin.y() = rnd.getFloat(-1.2f, 1.0f);
			bboxMin.z() = rnd.getFloat(-1.2f, 1.0f);
			bboxMin.w() = 1.0f;
			bboxMax.x() = bboxMin.x() + rnd.getFloat(0.2f, 1.0f);
			bboxMax.y() = bboxMin.y() + rnd.getFloat(0.2f, 1.0f);
			bboxMax.z() = bboxMin.z() + rnd.getFloat(0.2f, 1.0f);
			bboxMax.w() = 1.0f;
		}

		if (m_scissoredClear)
		{
			const int scissorX = (m_fullscreenScissor) ? (0)					: rnd.getInt(0, renderTargetSize.x()-1);
			const int scissorY = (m_fullscreenScissor) ? (0)					: rnd.getInt(0, renderTargetSize.y()-1);
			const int scissorW = (m_fullscreenScissor) ? (renderTargetSize.x())	: rnd.getInt(0, renderTargetSize.x()-scissorX);
			const int scissorH = (m_fullscreenScissor) ? (renderTargetSize.y())	: rnd.getInt(0, renderTargetSize.y()-scissorY);

			gl.scissor(scissorX, scissorY, scissorW, scissorH);
		}

		{
			const tcu::Vec4 color = tcu::mix(green, yellow, rnd.getFloat() * 0.4f); // greenish
			gl.clearColor(color.x(), color.y(), color.z(), color.w());
			gl.clear(GL_COLOR_BUFFER_BIT);
		}

		if (useBBox)
		{
			DE_ASSERT(m_useGlobalState || m_drawTriangles); // !m_useGlobalState -> m_drawTriangles
			if (m_useGlobalState)
				gl.primitiveBoundingBox(bboxMin.x(), bboxMin.y(), bboxMin.z(), bboxMin.w(),
										bboxMax.x(), bboxMax.y(), bboxMax.z(), bboxMax.w());
		}

		if (m_drawTriangles)
			gl.drawArrays(GL_PATCHES, objectVertexStartNdx, objectVertexLength);
	}

	GLU_EXPECT_NO_ERROR(gl.getError(), "post draw");
	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
}

bool ClearCase::verifyImagesEqual (const tcu::PixelBufferAccess& withoutBBox, const tcu::PixelBufferAccess& withBBox)
{
	DE_ASSERT(withoutBBox.getWidth() == withBBox.getWidth());
	DE_ASSERT(withoutBBox.getHeight() == withBBox.getHeight());

	tcu::Surface	errorMask	(withoutBBox.getWidth(), withoutBBox.getHeight());
	bool			anyError	= false;

	tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toIVec());

	for (int y = 0; y < withoutBBox.getHeight(); ++y)
	for (int x = 0; x < withoutBBox.getWidth(); ++x)
	{
		if (withoutBBox.getPixelInt(x, y) != withBBox.getPixelInt(x, y))
		{
			errorMask.setPixel(x, y, tcu::RGBA::red());
			anyError = true;
		}
	}

	if (anyError)
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Image comparison failed."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image comparison")
			<< tcu::TestLog::Image("WithoutBBox", "Result with bounding box not set", withoutBBox)
			<< tcu::TestLog::Image("WithBBox", "Result with bounding box set", withBBox)
			<< tcu::TestLog::Image("ErrorMask", "Error mask", errorMask.getAccess())
			<< tcu::TestLog::EndImageSet;
	}

	return !anyError;
}

bool ClearCase::verifyImageResultValid (const tcu::PixelBufferAccess& result)
{
	tcu::Surface	errorMask	(result.getWidth(), result.getHeight());
	bool			anyError	= false;

	tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toIVec());

	for (int y = 0; y < result.getHeight(); ++y)
	for (int x = 0; x < result.getWidth(); ++x)
	{
		const tcu::IVec4 pixel = result.getPixelInt(x, y);

		// allow green, yellow and any shade between
		if (pixel[1] != 255 || pixel[2] != 0)
		{
			errorMask.setPixel(x, y, tcu::RGBA::red());
			anyError = true;
		}
	}

	if (anyError)
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Image verification failed."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("ResultImage", "Result image", result)
			<< tcu::TestLog::Image("ErrorMask", "Error mask", errorMask)
			<< tcu::TestLog::EndImageSet;
	}

	return !anyError;
}

static const char* const s_yellowishPosOnlyVertexSource =	"#version 310 es\n"
															"in highp vec4 a_position;\n"
															"out highp vec4 v_vertex_color;\n"
															"void main()\n"
															"{\n"
															"	gl_Position = a_position;\n"
															"	// yellowish shade\n"
															"	highp float redComponent = 0.5 + float(gl_VertexID % 5) / 8.0;\n"
															"	v_vertex_color = vec4(redComponent, 1.0, 0.0, 1.0);\n"
															"}\n";

static const char* const s_basicColorFragmentSource =	"#version 310 es\n"
														"in mediump vec4 v_color;\n"
														"layout(location = 0) out mediump vec4 o_color;\n"
														"void main()\n"
														"{\n"
														"	o_color = v_color;\n"
														"}\n";


static const char* const s_basicColorTessEvalSource =	"#version 310 es\n"
														"#extension GL_EXT_tessellation_shader : require\n"
														"#extension GL_EXT_gpu_shader5 : require\n"
														"layout(triangles) in;\n"
														"in highp vec4 v_tess_eval_color[];\n"
														"out highp vec4 v_color;\n"
														"precise gl_Position;\n"
														"void main()\n"
														"{\n"
														"	gl_Position = gl_TessCoord.x * gl_in[0].gl_Position\n"
														"	            + gl_TessCoord.y * gl_in[1].gl_Position\n"
														"	            + gl_TessCoord.z * gl_in[2].gl_Position;\n"
														"	v_color = gl_TessCoord.x * v_tess_eval_color[0]\n"
														"	        + gl_TessCoord.y * v_tess_eval_color[1]\n"
														"	        + gl_TessCoord.z * v_tess_eval_color[2];\n"
														"}\n";

std::string ClearCase::genVertexSource (void) const
{
	return	s_yellowishPosOnlyVertexSource;
}

std::string ClearCase::genFragmentSource (void) const
{
	return s_basicColorFragmentSource;
}

std::string ClearCase::genTessellationControlSource (bool setBBox) const
{
	std::ostringstream buf;

	buf <<	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n";

	if (setBBox)
		buf << "#extension GL_EXT_primitive_bounding_box : require\n";

	buf <<	"layout(vertices=3) out;\n"
			"in highp vec4 v_vertex_color[];\n"
			"out highp vec4 v_tess_eval_color[];\n"
			"void main()\n"
			"{\n"
			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
			"	v_tess_eval_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n"
			"	gl_TessLevelOuter[0] = 2.8;\n"
			"	gl_TessLevelOuter[1] = 2.8;\n"
			"	gl_TessLevelOuter[2] = 2.8;\n"
			"	gl_TessLevelInner[0] = 2.8;\n";

	if (setBBox)
	{
		buf <<	"\n"
		"	gl_BoundingBoxEXT[0] = min(min(gl_in[0].gl_Position,\n"
		"	                               gl_in[1].gl_Position),\n"
		"	                           gl_in[2].gl_Position);\n"
		"	gl_BoundingBoxEXT[1] = max(max(gl_in[0].gl_Position,\n"
		"	                               gl_in[1].gl_Position),\n"
		"	                           gl_in[2].gl_Position);\n";
	}

	buf << "}\n";
	return buf.str();
}

std::string ClearCase::genTessellationEvaluationSource (void) const
{
	return s_basicColorTessEvalSource;
}

class ViewportCallOrderCase : public TestCase
{
public:
	enum CallOrder
	{
		VIEWPORT_FIRST = 0,
		BBOX_FIRST,

		ORDER_LAST
	};

									ViewportCallOrderCase			(Context& context, const char* name, const char* description, CallOrder callOrder);
									~ViewportCallOrderCase			(void);

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

	void							genVbo							(void);
	void							genProgram						(void);
	bool							verifyImage						(const tcu::PixelBufferAccess& result);

	std::string						genVertexSource					(void) const;
	std::string						genFragmentSource				(void) const;
	std::string						genTessellationControlSource	(void) const;
	std::string						genTessellationEvaluationSource	(void) const;

	const CallOrder					m_callOrder;

	de::MovePtr<glu::Buffer>		m_vbo;
	de::MovePtr<glu::ShaderProgram>	m_program;
	int								m_numVertices;
};

ViewportCallOrderCase::ViewportCallOrderCase (Context& context, const char* name, const char* description, CallOrder callOrder)
	: TestCase		(context, name, description)
	, m_callOrder	(callOrder)
	, m_numVertices	(-1)
{
	DE_ASSERT(m_callOrder < ORDER_LAST);
}

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

void ViewportCallOrderCase::init (void)
{
	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_primitive_bounding_box"))
		throw tcu::NotSupportedError("Test requires GL_EXT_primitive_bounding_box extension");

	if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
		throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Testing call order of state setting functions have no effect on the rendering.\n"
		<< "Setting viewport and bounding box in the following order:\n"
			<< ((m_callOrder == VIEWPORT_FIRST)
				? ("\tFirst viewport with glViewport function.\n")
				: ("\tFirst bounding box with glPrimitiveBoundingBoxEXT function.\n"))
			<< ((m_callOrder == VIEWPORT_FIRST)
				? ("\tThen bounding box with glPrimitiveBoundingBoxEXT function.\n")
				: ("\tThen viewport with glViewport function.\n"))
		<< "Verifying rendering result."
		<< tcu::TestLog::EndMessage;

	// resources
	genVbo();
	genProgram();
}

void ViewportCallOrderCase::deinit (void)
{
	m_vbo.clear();
	m_program.clear();
}

ViewportCallOrderCase::IterateResult ViewportCallOrderCase::iterate (void)
{
	const glw::Functions&	gl				= m_context.getRenderContext().getFunctions();
	const tcu::IVec2		viewportSize	= tcu::IVec2(m_context.getRenderTarget().getWidth(), m_context.getRenderTarget().getHeight());
	const glw::GLint		posLocation		= gl.getAttribLocation(m_program->getProgram(), "a_position");
	tcu::Surface			resultSurface	(viewportSize.x(), viewportSize.y());

	gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
	gl.clear(GL_COLOR_BUFFER_BIT);

	// set state
	for (int orderNdx = 0; orderNdx < 2; ++orderNdx)
	{
		if ((orderNdx == 0 && m_callOrder == VIEWPORT_FIRST) ||
			(orderNdx == 1 && m_callOrder == BBOX_FIRST))
		{
			m_testCtx.getLog()
				<< tcu::TestLog::Message
				<< "Setting viewport to cover the left half of the render target.\n"
				<< "\t(0, 0, " << (viewportSize.x()/2) << ", " << viewportSize.y() << ")"
				<< tcu::TestLog::EndMessage;

			gl.viewport(0, 0, viewportSize.x()/2, viewportSize.y());
		}
		else
		{
			m_testCtx.getLog()
				<< tcu::TestLog::Message
				<< "Setting bounding box to cover the right half of the clip space.\n"
				<< "\t(0.0, -1.0, -1.0, 1.0) .. (1.0, 1.0, 1.0f, 1.0)"
				<< tcu::TestLog::EndMessage;

			gl.primitiveBoundingBox(0.0f, -1.0f, -1.0f, 1.0f,
									1.0f,  1.0f,  1.0f, 1.0f);
		}
	}

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Rendering mesh covering the right half of the clip space."
		<< tcu::TestLog::EndMessage;

	gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
	gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, sizeof(float[4]), (const float*)DE_NULL);
	gl.enableVertexAttribArray(posLocation);
	gl.useProgram(m_program->getProgram());
	gl.patchParameteri(GL_PATCH_VERTICES, 3);
	gl.drawArrays(GL_PATCHES, 0, m_numVertices);
	GLU_EXPECT_NO_ERROR(gl.getError(), "post-draw");

	m_testCtx.getLog()
		<< tcu::TestLog::Message
		<< "Verifying image"
		<< tcu::TestLog::EndMessage;
	glu::readPixels(m_context.getRenderContext(), 0, 0, resultSurface.getAccess());

	if (!verifyImage(resultSurface.getAccess()))
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
	else
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Result ok."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("Result", "Result", resultSurface.getAccess())
			<< tcu::TestLog::EndImageSet;

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

void ViewportCallOrderCase::genVbo (void)
{
	const int				gridSize	= 6;
	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
	std::vector<tcu::Vec4>	data		(gridSize * gridSize * 2 * 3);
	std::vector<int>		cellOrder	(gridSize * gridSize * 2);
	de::Random				rnd			(0x55443322);

	// generate grid with triangles in random order
	for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
		cellOrder[ndx] = ndx;
	rnd.shuffle(cellOrder.begin(), cellOrder.end());

	// generate grid filling the right half of the clip space: (x: 0.0, y: -1.0) .. (x: 1.0, y: 1.0)
	for (int ndx = 0; ndx < (int)cellOrder.size(); ++ndx)
	{
		const int			cellNdx		= cellOrder[ndx];
		const bool			cellSide	= ((cellNdx % 2) == 0);
		const int			cellX		= (cellNdx / 2) % gridSize;
		const int			cellY		= (cellNdx / 2) / gridSize;

		if (cellSide)
		{
			data[ndx * 3 + 0] = tcu::Vec4(float(cellX+0) / float(gridSize), (float(cellY+0) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
			data[ndx * 3 + 1] = tcu::Vec4(float(cellX+1) / float(gridSize), (float(cellY+1) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
			data[ndx * 3 + 2] = tcu::Vec4(float(cellX+0) / float(gridSize), (float(cellY+1) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
		}
		else
		{
			data[ndx * 3 + 0] = tcu::Vec4(float(cellX+0) / float(gridSize), (float(cellY+0) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
			data[ndx * 3 + 1] = tcu::Vec4(float(cellX+1) / float(gridSize), (float(cellY+0) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
			data[ndx * 3 + 2] = tcu::Vec4(float(cellX+1) / float(gridSize), (float(cellY+1) / float(gridSize)) * 2.0f - 1.0f, 0.0f, 1.0f);
		}
	}

	m_vbo = de::MovePtr<glu::Buffer>(new glu::Buffer(m_context.getRenderContext()));
	gl.bindBuffer(GL_ARRAY_BUFFER, **m_vbo);
	gl.bufferData(GL_ARRAY_BUFFER, (int)(data.size() * sizeof(tcu::Vec4)), &data[0], GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR(gl.getError(), "create vbo");

	m_numVertices = (int)data.size();
}

void ViewportCallOrderCase::genProgram (void)
{
	m_program = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(m_context.getRenderContext(),
																	   glu::ProgramSources()
																			<< glu::VertexSource(genVertexSource())
																			<< glu::FragmentSource(genFragmentSource())
																			<< glu::TessellationControlSource(genTessellationControlSource())
																			<< glu::TessellationEvaluationSource(genTessellationEvaluationSource())));

	m_testCtx.getLog()
		<< tcu::TestLog::Section("Program", "Shader program")
		<< *m_program
		<< tcu::TestLog::EndSection;

	if (!m_program->isOk())
		throw tcu::TestError("shader build failed");
}

bool ViewportCallOrderCase::verifyImage (const tcu::PixelBufferAccess& result)
{
	const tcu::IVec2	insideBorder	(deCeilFloatToInt32(0.25f * (float)result.getWidth()) + 1, deFloorFloatToInt32(0.5f * (float)result.getWidth()) - 1);
	const tcu::IVec2	outsideBorder	(deFloorFloatToInt32(0.25f * (float)result.getWidth()) - 1, deCeilFloatToInt32(0.5f * (float)result.getWidth()) + 1);
	tcu::Surface		errorMask		(result.getWidth(), result.getHeight());
	bool				anyError		= false;

	tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toIVec());

	for (int y = 0; y < result.getHeight(); ++y)
	for (int x = 0; x < result.getWidth(); ++x)
	{
		const tcu::IVec4	pixel			= result.getPixelInt(x, y);
		const bool			insideMeshArea	= x >= insideBorder.x() && x <= insideBorder.x();
		const bool			outsideMeshArea = x <= outsideBorder.x() && x >= outsideBorder.x();

		// inside mesh, allow green, yellow and any shade between
		// outside mesh, allow background (black) only
		// in the border area, allow anything
		if ((insideMeshArea && (pixel[1] != 255 || pixel[2] != 0)) ||
			(outsideMeshArea && (pixel[0] != 0 || pixel[1] != 0 || pixel[2] != 0)))
		{
			errorMask.setPixel(x, y, tcu::RGBA::red());
			anyError = true;
		}
	}

	if (anyError)
	{
		m_testCtx.getLog()
			<< tcu::TestLog::Message
			<< "Image verification failed."
			<< tcu::TestLog::EndMessage
			<< tcu::TestLog::ImageSet("Images", "Image verification")
			<< tcu::TestLog::Image("ResultImage", "Result image", result)
			<< tcu::TestLog::Image("ErrorMask", "Error mask", errorMask)
			<< tcu::TestLog::EndImageSet;
	}

	return !anyError;
}

std::string ViewportCallOrderCase::genVertexSource (void) const
{
	return	s_yellowishPosOnlyVertexSource;
}

std::string ViewportCallOrderCase::genFragmentSource (void) const
{
	return s_basicColorFragmentSource;
}

std::string ViewportCallOrderCase::genTessellationControlSource (void) const
{
	return	"#version 310 es\n"
			"#extension GL_EXT_tessellation_shader : require\n"
			"layout(vertices=3) out;\n"
			"in highp vec4 v_vertex_color[];\n"
			"out highp vec4 v_tess_eval_color[];\n"
			"void main()\n"
			"{\n"
			"	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
			"	v_tess_eval_color[gl_InvocationID] = v_vertex_color[gl_InvocationID];\n"
			"	gl_TessLevelOuter[0] = 2.8;\n"
			"	gl_TessLevelOuter[1] = 2.8;\n"
			"	gl_TessLevelOuter[2] = 2.8;\n"
			"	gl_TessLevelInner[0] = 2.8;\n"
			"}\n";
}

std::string ViewportCallOrderCase::genTessellationEvaluationSource (void) const
{
	return s_basicColorTessEvalSource;
}

} // anonymous

PrimitiveBoundingBoxTests::PrimitiveBoundingBoxTests (Context& context)
	: TestCaseGroup(context, "primitive_bounding_box", "Tests for EXT_primitive_bounding_box")
{
}

PrimitiveBoundingBoxTests::~PrimitiveBoundingBoxTests (void)
{
}

void PrimitiveBoundingBoxTests::init (void)
{
	static const struct
	{
		const char*	name;
		const char*	description;
		deUint32	methodFlags;
	} stateSetMethods[] =
	{
		{
			"global_state",
			"Set bounding box using PRIMITIVE_BOUNDING_BOX_EXT state",
			BBoxRenderCase::FLAG_SET_BBOX_STATE,
		},
		{
			"tessellation_set_per_draw",
			"Set bounding box using gl_BoundingBoxEXT, use same value for all primitives",
			BBoxRenderCase::FLAG_SET_BBOX_OUTPUT,
		},
		{
			"tessellation_set_per_primitive",
			"Set bounding box using gl_BoundingBoxEXT, use per-primitive bounding box",
			BBoxRenderCase::FLAG_SET_BBOX_OUTPUT | BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX,
		},
	};
	static const struct
	{
		const char*	name;
		const char*	description;
		deUint32	stageFlags;
	} pipelineConfigs[] =
	{
		{
			"vertex_fragment",
			"Render with vertex-fragment program",
			0u
		},
		{
			"vertex_tessellation_fragment",
			"Render with vertex-tessellation{ctrl,eval}-fragment program",
			BBoxRenderCase::FLAG_TESSELLATION
		},
		{
			"vertex_geometry_fragment",
			"Render with vertex-tessellation{ctrl,eval}-geometry-fragment program",
			BBoxRenderCase::FLAG_GEOMETRY
		},
		{
			"vertex_tessellation_geometry_fragment",
			"Render with vertex-geometry-fragment program",
			BBoxRenderCase::FLAG_TESSELLATION | BBoxRenderCase::FLAG_GEOMETRY
		},
	};
	static const struct
	{
		const char*	name;
		const char*	description;
		deUint32	flags;
		deUint32	invalidFlags;
		deUint32	requiredFlags;
	} usageConfigs[] =
	{
		{
			"default_framebuffer_bbox_equal",
			"Render to default framebuffer, set tight bounding box",
			BBoxRenderCase::FLAG_RENDERTARGET_DEFAULT | BBoxRenderCase::FLAG_BBOXSIZE_EQUAL,
			BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX,
			0
		},
		{
			"default_framebuffer_bbox_larger",
			"Render to default framebuffer, set padded bounding box",
			BBoxRenderCase::FLAG_RENDERTARGET_DEFAULT | BBoxRenderCase::FLAG_BBOXSIZE_LARGER,
			BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX,
			0
		},
		{
			"default_framebuffer_bbox_smaller",
			"Render to default framebuffer, set too small bounding box",
			BBoxRenderCase::FLAG_RENDERTARGET_DEFAULT | BBoxRenderCase::FLAG_BBOXSIZE_SMALLER,
			BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX,
			0
		},
		{
			"fbo_bbox_equal",
			"Render to texture, set tight bounding box",
			BBoxRenderCase::FLAG_RENDERTARGET_FBO | BBoxRenderCase::FLAG_BBOXSIZE_EQUAL,
			BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX,
			0
		},
		{
			"fbo_bbox_larger",
			"Render to texture, set padded bounding box",
			BBoxRenderCase::FLAG_RENDERTARGET_FBO | BBoxRenderCase::FLAG_BBOXSIZE_LARGER,
			BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX,
			0
		},
		{
			"fbo_bbox_smaller",
			"Render to texture, set too small bounding box",
			BBoxRenderCase::FLAG_RENDERTARGET_FBO | BBoxRenderCase::FLAG_BBOXSIZE_SMALLER,
			BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX,
			0
		},
		{
			"default_framebuffer",
			"Render to default framebuffer, set tight bounding box",
			BBoxRenderCase::FLAG_RENDERTARGET_DEFAULT | BBoxRenderCase::FLAG_BBOXSIZE_EQUAL,
			0,
			BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX
		},
		{
			"fbo",
			"Render to texture, set tight bounding box",
			BBoxRenderCase::FLAG_RENDERTARGET_FBO | BBoxRenderCase::FLAG_BBOXSIZE_EQUAL,
			0,
			BBoxRenderCase::FLAG_PER_PRIMITIVE_BBOX
		},
	};
	enum PrimitiveRenderType
	{
		TYPE_TRIANGLE,
		TYPE_LINE,
		TYPE_POINT,
	};
	const struct
	{
		const char*			name;
		const char*			description;
		PrimitiveRenderType	type;
		deUint32			flags;
	} primitiveTypes[] =
	{
		{
			"triangles",
			"Triangle render tests",
			TYPE_TRIANGLE,
			0
		},
		{
			"lines",
			"Line render tests",
			TYPE_LINE,
			0
		},
		{
			"points",
			"Point render tests",
			TYPE_POINT,
			0
		},
		{
			"wide_lines",
			"Wide line render tests",
			TYPE_LINE,
			LineRenderCase::LINEFLAG_WIDE
		},
		{
			"wide_points",
			"Wide point render tests",
			TYPE_POINT,
			PointRenderCase::POINTFLAG_WIDE
		},
	};

	// .state_query
	{
		tcu::TestCaseGroup* const stateQueryGroup = new tcu::TestCaseGroup(m_testCtx, "state_query", "State queries");
		addChild(stateQueryGroup);

		stateQueryGroup->addChild(new InitialValueCase	(m_context,	"initial_value",	"Initial value case"));
		stateQueryGroup->addChild(new QueryCase			(m_context,	"getfloat",			"getFloatv",			QueryCase::QUERY_FLOAT));
		stateQueryGroup->addChild(new QueryCase			(m_context,	"getboolean",		"getBooleanv",			QueryCase::QUERY_BOOLEAN));
		stateQueryGroup->addChild(new QueryCase			(m_context,	"getinteger",		"getIntegerv",			QueryCase::QUERY_INT));
		stateQueryGroup->addChild(new QueryCase			(m_context,	"getinteger64",		"getInteger64v",		QueryCase::QUERY_INT64));
	}

	// .triangles
	// .(wide_)lines
	// .(wide_)points
	for (int primitiveTypeNdx = 0; primitiveTypeNdx < DE_LENGTH_OF_ARRAY(primitiveTypes); ++primitiveTypeNdx)
	{
		tcu::TestCaseGroup* const primitiveGroup = new tcu::TestCaseGroup(m_testCtx, primitiveTypes[primitiveTypeNdx].name, primitiveTypes[primitiveTypeNdx].description);
		addChild(primitiveGroup);

		for (int stateSetMethodNdx = 0; stateSetMethodNdx < DE_LENGTH_OF_ARRAY(stateSetMethods); ++stateSetMethodNdx)
		{
			tcu::TestCaseGroup* const methodGroup = new tcu::TestCaseGroup(m_testCtx, stateSetMethods[stateSetMethodNdx].name, stateSetMethods[stateSetMethodNdx].description);
			primitiveGroup->addChild(methodGroup);

			for (int pipelineConfigNdx = 0; pipelineConfigNdx < DE_LENGTH_OF_ARRAY(pipelineConfigs); ++pipelineConfigNdx)
			{
				if ((stateSetMethods[stateSetMethodNdx].methodFlags & BBoxRenderCase::FLAG_SET_BBOX_OUTPUT) != 0 &&
					(pipelineConfigs[pipelineConfigNdx].stageFlags  & BBoxRenderCase::FLAG_TESSELLATION)    == 0)
				{
					// invalid config combination
				}
				else
				{
					tcu::TestCaseGroup* const pipelineGroup = new tcu::TestCaseGroup(m_testCtx, pipelineConfigs[pipelineConfigNdx].name, pipelineConfigs[pipelineConfigNdx].description);
					methodGroup->addChild(pipelineGroup);

					for (int usageNdx = 0; usageNdx < DE_LENGTH_OF_ARRAY(usageConfigs); ++usageNdx)
					{
						const deUint32 flags = primitiveTypes[primitiveTypeNdx].flags         |
											   stateSetMethods[stateSetMethodNdx].methodFlags |
											   pipelineConfigs[pipelineConfigNdx].stageFlags  |
											   usageConfigs[usageNdx].flags;

						if (usageConfigs[usageNdx].invalidFlags && (flags & usageConfigs[usageNdx].invalidFlags) != 0)
							continue;
						if (usageConfigs[usageNdx].requiredFlags && (flags & usageConfigs[usageNdx].requiredFlags) == 0)
							continue;

						switch (primitiveTypes[primitiveTypeNdx].type)
						{
							case TYPE_TRIANGLE:
								pipelineGroup->addChild(new GridRenderCase(m_context, usageConfigs[usageNdx].name, usageConfigs[usageNdx].description, flags));
								break;
							case TYPE_LINE:
								pipelineGroup->addChild(new LineRenderCase(m_context, usageConfigs[usageNdx].name, usageConfigs[usageNdx].description, flags));
								break;
							case TYPE_POINT:
								pipelineGroup->addChild(new PointRenderCase(m_context, usageConfigs[usageNdx].name, usageConfigs[usageNdx].description, flags));
								break;
							default:
								DE_ASSERT(false);
						}
					}
				}
			}
		}
	}

	// .depth
	{
		static const struct
		{
			const char*					name;
			const char*					description;
			DepthDrawCase::DepthType	depthMethod;
		} depthMethods[] =
		{
			{
				"builtin_depth",
				"Fragment depth not modified in fragment shader",
				DepthDrawCase::DEPTH_BUILTIN
			},
			{
				"user_defined_depth",
				"Fragment depth is defined in the fragment shader",
				DepthDrawCase::DEPTH_USER_DEFINED
			},
		};
		static const struct
		{
			const char*					name;
			const char*					description;
			DepthDrawCase::BBoxState	bboxState;
			DepthDrawCase::BBoxSize		bboxSize;
		} depthCases[] =
		{
			{
				"global_state_bbox_equal",
				"Test tight bounding box with global bbox state",
				DepthDrawCase::STATE_GLOBAL,
				DepthDrawCase::BBOX_EQUAL,
			},
			{
				"global_state_bbox_larger",
				"Test padded bounding box with global bbox state",
				DepthDrawCase::STATE_GLOBAL,
				DepthDrawCase::BBOX_LARGER,
			},
			{
				"per_primitive_bbox_equal",
				"Test tight bounding box with tessellation output bbox",
				DepthDrawCase::STATE_PER_PRIMITIVE,
				DepthDrawCase::BBOX_EQUAL,
			},
			{
				"per_primitive_bbox_larger",
				"Test padded bounding box with tessellation output bbox",
				DepthDrawCase::STATE_PER_PRIMITIVE,
				DepthDrawCase::BBOX_LARGER,
			},
		};

		tcu::TestCaseGroup* const depthGroup = new tcu::TestCaseGroup(m_testCtx, "depth", "Test bounding box depth component");
		addChild(depthGroup);

		// .builtin_depth
		// .user_defined_depth
		for (int depthNdx = 0; depthNdx < DE_LENGTH_OF_ARRAY(depthMethods); ++depthNdx)
		{
			tcu::TestCaseGroup* const group = new tcu::TestCaseGroup(m_testCtx, depthMethods[depthNdx].name, depthMethods[depthNdx].description);
			depthGroup->addChild(group);

			for (int caseNdx = 0; caseNdx < DE_LENGTH_OF_ARRAY(depthCases); ++caseNdx)
				group->addChild(new DepthDrawCase(m_context, depthCases[caseNdx].name, depthCases[caseNdx].description, depthMethods[depthNdx].depthMethod, depthCases[caseNdx].bboxState, depthCases[caseNdx].bboxSize));
		}
	}

	// .blit_fbo
	{
		tcu::TestCaseGroup* const blitFboGroup = new tcu::TestCaseGroup(m_testCtx, "blit_fbo", "Test bounding box does not affect blitting");
		addChild(blitFboGroup);

		blitFboGroup->addChild(new BlitFboCase(m_context, "blit_default_to_fbo", "Blit from default fb to fbo", BlitFboCase::TARGET_DEFAULT, BlitFboCase::TARGET_FBO));
		blitFboGroup->addChild(new BlitFboCase(m_context, "blit_fbo_to_default", "Blit from fbo to default fb", BlitFboCase::TARGET_FBO,     BlitFboCase::TARGET_DEFAULT));
		blitFboGroup->addChild(new BlitFboCase(m_context, "blit_fbo_to_fbo",     "Blit from fbo to fbo",        BlitFboCase::TARGET_FBO,     BlitFboCase::TARGET_FBO));
	}

	// .clear
	{
		tcu::TestCaseGroup* const clearGroup = new tcu::TestCaseGroup(m_testCtx, "clear", "Test bounding box does not clears");
		addChild(clearGroup);

		clearGroup->addChild(new ClearCase(m_context, "full_clear",                                             "Do full clears",                                               0));
		clearGroup->addChild(new ClearCase(m_context, "full_clear_with_triangles",                              "Do full clears and render some geometry",                      ClearCase::DRAW_TRIANGLE_BIT));
		clearGroup->addChild(new ClearCase(m_context, "full_clear_with_triangles_per_primitive_bbox",           "Do full clears and render some geometry",                      ClearCase::DRAW_TRIANGLE_BIT | ClearCase::PER_PRIMITIVE_BBOX_BIT));
		clearGroup->addChild(new ClearCase(m_context, "scissored_clear",                                        "Do scissored clears",                                          ClearCase::SCISSOR_CLEAR_BIT));
		clearGroup->addChild(new ClearCase(m_context, "scissored_clear_with_triangles",                         "Do scissored clears and render some geometry",                 ClearCase::SCISSOR_CLEAR_BIT | ClearCase::DRAW_TRIANGLE_BIT));
		clearGroup->addChild(new ClearCase(m_context, "scissored_clear_with_triangles_per_primitive_bbox",      "Do scissored clears and render some geometry",                 ClearCase::SCISSOR_CLEAR_BIT | ClearCase::DRAW_TRIANGLE_BIT | ClearCase::PER_PRIMITIVE_BBOX_BIT));
		clearGroup->addChild(new ClearCase(m_context, "scissored_full_clear",                                   "Do full clears with enabled scissor",                          ClearCase::FULLSCREEN_SCISSOR_BIT | ClearCase::SCISSOR_CLEAR_BIT));
		clearGroup->addChild(new ClearCase(m_context, "scissored_full_clear_with_triangles",                    "Do full clears with enabled scissor and render some geometry", ClearCase::FULLSCREEN_SCISSOR_BIT | ClearCase::SCISSOR_CLEAR_BIT | ClearCase::DRAW_TRIANGLE_BIT));
		clearGroup->addChild(new ClearCase(m_context, "scissored_full_clear_with_triangles_per_primitive_bbox", "Do full clears with enabled scissor and render some geometry", ClearCase::FULLSCREEN_SCISSOR_BIT | ClearCase::SCISSOR_CLEAR_BIT | ClearCase::DRAW_TRIANGLE_BIT | ClearCase::PER_PRIMITIVE_BBOX_BIT));
	}

	// .call_order (Khronos bug #13262)
	{
		tcu::TestCaseGroup* const callOrderGroup = new tcu::TestCaseGroup(m_testCtx, "call_order", "Test viewport and bounding box calls have no effect");
		addChild(callOrderGroup);

		callOrderGroup->addChild(new ViewportCallOrderCase(m_context, "viewport_first_bbox_second", "Set up viewport first and bbox after", ViewportCallOrderCase::VIEWPORT_FIRST));
		callOrderGroup->addChild(new ViewportCallOrderCase(m_context, "bbox_first_viewport_second", "Set up bbox first and viewport after", ViewportCallOrderCase::BBOX_FIRST));
	}
}

} // Functional
} // gles31
} // deqp